Compare commits
174 Commits
main
...
30109fdb79
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30109fdb79 | ||
|
|
83226f006c | ||
|
|
a5e21c93b3 | ||
|
|
1989c22562 | ||
|
|
882740592c | ||
|
|
0c995054a2 | ||
|
|
6780c0fbb1 | ||
|
|
7101daeb90 | ||
|
|
0d0de45a25 | ||
|
|
28b6153ab0 | ||
|
|
7a4fc0736d | ||
|
|
9912f310ec | ||
|
|
7bf1a0bd57 | ||
|
|
810dd2351b | ||
|
|
e42975159f | ||
|
|
c1cff1d61b | ||
|
|
dbe4557dc3 | ||
|
|
bc7099cce2 | ||
|
|
d75e956fbf | ||
|
|
6eda04a81e | ||
|
|
069b86de13 | ||
|
|
833d43d7d1 | ||
| c9b67c4d3b | |||
| a8510445cd | |||
|
|
e1ca896764 | ||
|
|
7a6bd28de5 | ||
|
|
85a158ea3e | ||
|
|
7fc0e3bace | ||
|
|
7af8bc96c8 | ||
|
|
64ac0c7e16 | ||
|
|
7b071bc585 | ||
|
|
3058adfdb7 | ||
|
|
88bd58fc66 | ||
|
|
274ea15dcc | ||
|
|
d863376b41 | ||
|
|
5bbc71654a | ||
|
|
9d41602320 | ||
|
|
4e0faed88e | ||
| 3fa7d407d2 | |||
| 1fa60557df | |||
| 2b273ec70a | |||
| 45af83d0b2 | |||
| b4ea8907d7 | |||
| 7ed87f59ee | |||
|
|
567ae02c48 | ||
|
|
ae6f14efa9 | ||
| 048a548df8 | |||
| b9112a5606 | |||
|
|
8f1fea30ee | ||
|
|
29704f9b36 | ||
| 6cd54cda18 | |||
| 38c0b88abf | |||
|
|
e40b707501 | ||
|
|
6cb1e72798 | ||
|
|
11876f7fff | ||
|
|
59541a9d3d | ||
|
|
466d278b29 | ||
| 6fa5ade5b1 | |||
| c6b1efe719 | |||
| fa3063b3b5 | |||
| ebd5ceac41 | |||
| 9c9b219f3b | |||
|
|
73aca07391 | ||
|
|
780270882e | ||
|
|
f8e4ab8cdb | ||
|
|
bb53b6e486 | ||
|
|
e09c01cb7d | ||
|
|
2903553088 | ||
|
|
8c5105052d | ||
|
|
0e1e2cec39 | ||
|
|
086668d31b | ||
| d4da1b47ef | |||
| 1596b46ff1 | |||
| fd0ec4f7ff | |||
|
|
012c4036e0 | ||
| 688fb3daa0 | |||
|
|
5e5059ea73 | ||
|
|
386a103df1 | ||
| aad884d07c | |||
| f011300bef | |||
|
|
15f5c6b3a2 | ||
|
|
7fbd721512 | ||
| 271b8af4c4 | |||
| 81e230b79f | |||
|
|
7da3dcb0d7 | ||
| c6f3a44b81 | |||
| 0e16681404 | |||
|
|
b8f53e9f4a | ||
|
|
697dc36df4 | ||
|
|
5672307e33 | ||
|
|
fd80e2d3c7 | ||
| b160709f16 | |||
|
|
b52c96fa67 | ||
| 5a7e5e92a8 | |||
| 61dd9fb1c5 | |||
|
|
3d202e32c2 | ||
| 07b2334d61 | |||
| 892d96b904 | |||
| 41a42b1133 | |||
|
|
467ac9c24f | ||
|
|
1d4478e98e | ||
|
|
e24318e8ee | ||
|
|
5ad2e40221 | ||
|
|
c82afcbfd6 | ||
|
|
95d85572f3 | ||
|
|
727636e0f8 | ||
|
|
b01a375acc | ||
|
|
b3c396ba9c | ||
|
|
260db8e896 | ||
|
|
b5f393ceb7 | ||
| b1bea281ec | |||
| c9b65b6090 | |||
| 0ac6d6e93f | |||
| 743b3f0ef6 | |||
| 17f0045dbe | |||
|
|
1ae365b1f3 | ||
| b16c5c3263 | |||
| 8ed58d37d8 | |||
|
|
84175e94d1 | ||
|
|
ed83044f81 | ||
| 9cb6be3098 | |||
| 4ab4578081 | |||
|
|
652d89d3be | ||
|
|
17edeef461 | ||
|
|
aad6919ec3 | ||
|
|
baf161e695 | ||
|
|
51751f6b5e | ||
|
|
c1b051a185 | ||
| 1a5e285f09 | |||
| 911d1d8477 | |||
| e0261d4a37 | |||
| 50cb33ac43 | |||
| dcb63f88ae | |||
|
|
8dd9ddc93e | ||
|
|
2dc6bd1346 | ||
|
|
703d9cf781 | ||
| 261064bd23 | |||
| a1e8f3295e | |||
| a89c199ea8 | |||
| 5dc7514f05 | |||
| 925541ab99 | |||
| b800ca6b74 | |||
| 4a4afc4b10 | |||
| 26a55cea1d | |||
| 5e68456707 | |||
| e992aa0ecd | |||
|
|
69c32905e1 | ||
|
|
c582de3f60 | ||
|
|
0a8074eef8 | ||
| 4b90bd5928 | |||
| 4376c8c313 | |||
| 4746ff22a1 | |||
| 68f8a413bf | |||
| c10d05ead2 | |||
| de641d18d7 | |||
|
|
8a0beee181 | ||
|
|
3dcb6330e3 | ||
|
|
4bd8a54b34 | ||
|
|
8368c9382a | ||
| f2463da8cc | |||
| 6b8027f449 | |||
|
|
8c3fea8a24 | ||
|
|
819093db8c | ||
|
|
7dcfc3e705 | ||
|
|
7bb8b227b4 | ||
|
|
3d2fddbe7b | ||
|
|
9662610b1b | ||
|
|
56f958173b | ||
|
|
0e57e4de46 | ||
|
|
b0e365dcde | ||
|
|
5497f4fdbc | ||
|
|
3d6b622eef | ||
|
|
38ac7da504 | ||
|
|
1c895710d8 |
@@ -1,90 +0,0 @@
|
|||||||
name: git commit 控制 AiDA WEB-Node.js 开发分支构建部署
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev_vite
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: "contains(github.event.head_commit.message, '[run build]')"
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [ 18.18.0 ]
|
|
||||||
env:
|
|
||||||
REMOTE_DEPLOY_PATH: /workspace/workspace_aida/DevelopVersion/develop-aida-web-front
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 0.记录开始时间
|
|
||||||
id: build_start_time
|
|
||||||
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 1.检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: dev_vite
|
|
||||||
|
|
||||||
- name: 2.设置 Node.js 环境
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build:dev
|
|
||||||
- run: ls -l
|
|
||||||
|
|
||||||
- name: 3.同步文件到远程服务器
|
|
||||||
uses: appleboy/scp-action@v0.1.7
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
source: "./dist/*"
|
|
||||||
target: ${{ env.REMOTE_DEPLOY_PATH }}
|
|
||||||
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
||||||
strip_components: 0
|
|
||||||
|
|
||||||
- name: 4. 远程重载 Nginx 配置
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
# 核心:执行 Nginx 重载命令
|
|
||||||
script: |
|
|
||||||
echo "尝试重载 Nginx 服务..."
|
|
||||||
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
|
|
||||||
# 否则工作流可能会因为权限不足而失败。
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
echo "Nginx 重载命令已发送。"
|
|
||||||
|
|
||||||
- name: 5.发送构建结果邮件
|
|
||||||
if: always() # 无论上一步是否失败,都执行此步骤
|
|
||||||
uses: dawidd6/action-send-mail@v3
|
|
||||||
with:
|
|
||||||
|
|
||||||
from: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# --- 邮件配置 ---
|
|
||||||
server_address: smtp.gmail.com # 替换为你的SMTP服务器地址
|
|
||||||
server_port: 465 # 替换为你的SMTP端口 (通常是465或587)
|
|
||||||
username: ${{ secrets.MAIL_USERNAME }} # 存储在Secrets中的邮箱用户名
|
|
||||||
password: ${{ secrets.MAIL_PASSWORD }} # 存储在Secrets中的邮箱密码
|
|
||||||
subject: 'Gitea Actions 构建通知: ${{ job.status }} - AiDA back-java Develop'
|
|
||||||
# 收件人列表,可以根据需要更改
|
|
||||||
to: 'xupei3360@163.com,txli@aidlab.hk,cgzhou@aidlab.hk,zchengrong@yeah.net' # 替换为实际收件人邮箱
|
|
||||||
|
|
||||||
# --- 邮件正文内容 ---
|
|
||||||
body: |
|
|
||||||
项目: AiDA back-java Develop
|
|
||||||
分支: dev/3.1_release_merge
|
|
||||||
|
|
||||||
🎉 构建结果: ${{ job.status }}
|
|
||||||
|
|
||||||
📅 构建时间: ${{ steps.build_start_time.outputs.current_time }}
|
|
||||||
|
|
||||||
🔗 构建链接: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
|
|
||||||
|
|
||||||
# 确保邮件内容为纯文本,或者你可以设置为 html: true 并调整 body
|
|
||||||
content_type: text/plain
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
name: 手动触发 AiDA WEB-Node.js 开发分支构建部署
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [ 18.18.0 ]
|
|
||||||
env:
|
|
||||||
REMOTE_DEPLOY_PATH: /workspace/workspace_aida/DevelopVersion/develop-aida-web-front
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 0.记录开始时间
|
|
||||||
id: build_start_time
|
|
||||||
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 1.检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: dev_vite
|
|
||||||
|
|
||||||
- name: 2.设置 Node.js 环境
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build:dev
|
|
||||||
- run: ls -l
|
|
||||||
|
|
||||||
- name: 3.同步文件到远程服务器
|
|
||||||
uses: appleboy/scp-action@v0.1.7
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
source: "./dist/*"
|
|
||||||
target: ${{ env.REMOTE_DEPLOY_PATH }}
|
|
||||||
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
||||||
strip_components: 0
|
|
||||||
|
|
||||||
- name: 4. 远程重载 Nginx 配置
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
# 核心:执行 Nginx 重载命令
|
|
||||||
script: |
|
|
||||||
echo "尝试重载 Nginx 服务..."
|
|
||||||
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
|
|
||||||
# 否则工作流可能会因为权限不足而失败。
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
echo "Nginx 重载命令已发送。"
|
|
||||||
|
|
||||||
- name: 5.发送构建结果邮件
|
|
||||||
if: always() # 无论上一步是否失败,都执行此步骤
|
|
||||||
uses: dawidd6/action-send-mail@v3
|
|
||||||
with:
|
|
||||||
|
|
||||||
from: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# --- 邮件配置 ---
|
|
||||||
server_address: smtp.gmail.com # 替换为你的SMTP服务器地址
|
|
||||||
server_port: 465 # 替换为你的SMTP端口 (通常是465或587)
|
|
||||||
username: ${{ secrets.MAIL_USERNAME }} # 存储在Secrets中的邮箱用户名
|
|
||||||
password: ${{ secrets.MAIL_PASSWORD }} # 存储在Secrets中的邮箱密码
|
|
||||||
subject: 'Gitea Actions 构建通知: ${{ job.status }} - AiDA back-java Develop'
|
|
||||||
# 收件人列表,可以根据需要更改
|
|
||||||
to: 'xupei3360@163.com,txli@aidlab.hk,cgzhou@aidlab.hk,zchengrong@yeah.net' # 替换为实际收件人邮箱
|
|
||||||
|
|
||||||
# --- 邮件正文内容 ---
|
|
||||||
body: |
|
|
||||||
项目: AiDA back-java Develop
|
|
||||||
分支: dev/3.1_release_merge
|
|
||||||
|
|
||||||
🎉 构建结果: ${{ job.status }}
|
|
||||||
|
|
||||||
📅 构建时间: ${{ steps.build_start_time.outputs.current_time }}
|
|
||||||
|
|
||||||
🔗 构建链接: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
|
|
||||||
|
|
||||||
# 确保邮件内容为纯文本,或者你可以设置为 html: true 并调整 body
|
|
||||||
content_type: text/plain
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
name: AiDA WEB-Node.js 生产分支构建部署
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [ 18.18.0 ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 0.记录开始时间
|
|
||||||
id: build_start_time
|
|
||||||
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 1.检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: StableVersion
|
|
||||||
|
|
||||||
- name: 2.设置 Node.js 环境
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build
|
|
||||||
- run: ls -l
|
|
||||||
|
|
||||||
- name: 3.5. 手动安装 AWS CLI v2 # 新增步骤:确保 aws 命令可用
|
|
||||||
run: |
|
|
||||||
echo "安装 AWS CLI V2..."
|
|
||||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
||||||
unzip awscliv2.zip
|
|
||||||
sudo ./aws/install --update
|
|
||||||
aws --version
|
|
||||||
echo "AWS CLI V2 安装完成。"
|
|
||||||
|
|
||||||
- name: 4.配置 AWS 凭证
|
|
||||||
uses: aws-actions/configure-aws-credentials@main
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: 'ap-east-1'
|
|
||||||
|
|
||||||
- name: 5.同步 dist 目录到 S3
|
|
||||||
run: |
|
|
||||||
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_NAME }}/ --acl public-read
|
|
||||||
|
|
||||||
- name: 6.发送构建结果邮件
|
|
||||||
if: always() # 无论上一步是否失败,都执行此步骤
|
|
||||||
uses: dawidd6/action-send-mail@v3
|
|
||||||
with:
|
|
||||||
|
|
||||||
from: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# --- 邮件配置 ---
|
|
||||||
server_address: smtp.gmail.com # 替换为你的SMTP服务器地址
|
|
||||||
server_port: 465 # 替换为你的SMTP端口 (通常是465或587)
|
|
||||||
username: ${{ secrets.MAIL_USERNAME }} # 存储在Secrets中的邮箱用户名
|
|
||||||
password: ${{ secrets.MAIL_PASSWORD }} # 存储在Secrets中的邮箱密码
|
|
||||||
subject: 'Gitea Actions 构建通知: ${{ job.status }} - AiDA back-java Develop'
|
|
||||||
# 收件人列表,可以根据需要更改
|
|
||||||
to: 'xupei3360@163.com,txli@aidlab.hk,cgzhou@aidlab.hk,zchengrong@yeah.net' # 替换为实际收件人邮箱
|
|
||||||
|
|
||||||
# --- 邮件正文内容 ---
|
|
||||||
body: |
|
|
||||||
项目: AiDA back-java Develop
|
|
||||||
分支: dev/3.1_release_merge
|
|
||||||
|
|
||||||
🎉 构建结果: ${{ job.status }}
|
|
||||||
|
|
||||||
📅 构建时间: ${{ steps.build_start_time.outputs.current_time }}
|
|
||||||
|
|
||||||
🔗 构建链接: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
|
|
||||||
|
|
||||||
# 确保邮件内容为纯文本,或者你可以设置为 html: true 并调整 body
|
|
||||||
content_type: text/plain
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
name: AiDA WEB-Node.js 生产分支构建部署
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
# cron为UTC时区,构建时间=部署时间-8小时 {*分 (-8)时 *日 *月 *周} ---
|
|
||||||
# 示例: 1月1日22点22分触发构建 cron写作 - '22 14 1 1 *'
|
|
||||||
- cron: '00 14 23 3 *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [ 18.18.0 ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 0.记录开始时间
|
|
||||||
id: build_start_time
|
|
||||||
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 1.检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: StableVersion
|
|
||||||
|
|
||||||
- name: 2.设置 Node.js 环境
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build
|
|
||||||
- run: ls -l
|
|
||||||
|
|
||||||
- name: 3.5. 手动安装 AWS CLI v2 # 新增步骤:确保 aws 命令可用
|
|
||||||
run: |
|
|
||||||
echo "安装 AWS CLI V2..."
|
|
||||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
||||||
unzip awscliv2.zip
|
|
||||||
sudo ./aws/install --update
|
|
||||||
aws --version
|
|
||||||
echo "AWS CLI V2 安装完成。"
|
|
||||||
|
|
||||||
- name: 4.配置 AWS 凭证
|
|
||||||
uses: aws-actions/configure-aws-credentials@main
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: 'ap-east-1'
|
|
||||||
|
|
||||||
- name: 5.同步 dist 目录到 S3
|
|
||||||
run: |
|
|
||||||
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_NAME }}/ --acl public-read
|
|
||||||
|
|
||||||
- name: 6.发送构建结果邮件
|
|
||||||
if: always() # 无论上一步是否失败,都执行此步骤
|
|
||||||
uses: dawidd6/action-send-mail@v3
|
|
||||||
with:
|
|
||||||
|
|
||||||
from: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# --- 邮件配置 ---
|
|
||||||
server_address: smtp.gmail.com # 替换为你的SMTP服务器地址
|
|
||||||
server_port: 465 # 替换为你的SMTP端口 (通常是465或587)
|
|
||||||
username: ${{ secrets.MAIL_USERNAME }} # 存储在Secrets中的邮箱用户名
|
|
||||||
password: ${{ secrets.MAIL_PASSWORD }} # 存储在Secrets中的邮箱密码
|
|
||||||
subject: 'Gitea Actions 构建通知: ${{ job.status }} - AiDA back-java Develop'
|
|
||||||
# 收件人列表,可以根据需要更改
|
|
||||||
to: 'cgzhou@aidlab.hk,zchengrong@yeah.net' # 替换为实际收件人邮箱
|
|
||||||
|
|
||||||
# --- 邮件正文内容 ---
|
|
||||||
body: |
|
|
||||||
项目: AiDA WEB-Node.js 生产分支构建部署
|
|
||||||
分支: StableVersion
|
|
||||||
|
|
||||||
🎉 构建结果: ${{ job.status }}
|
|
||||||
|
|
||||||
📅 构建时间: ${{ steps.build_start_time.outputs.current_time }}
|
|
||||||
|
|
||||||
🔗 构建链接: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
|
|
||||||
|
|
||||||
# 确保邮件内容为纯文本,或者你可以设置为 html: true 并调整 body
|
|
||||||
content_type: text/plain
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
name: 手动触发 AiDA WEB-Node.js 开发分支构建部署
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [ 18.18.0 ]
|
|
||||||
env:
|
|
||||||
REMOTE_DEPLOY_PATH: /workspace/workspace_aida/Research/research-aida-web-front
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 0.记录开始时间
|
|
||||||
id: build_start_time
|
|
||||||
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 1.检出代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: research
|
|
||||||
|
|
||||||
- name: 2.设置 Node.js 环境
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run build:dev
|
|
||||||
- run: ls -l
|
|
||||||
|
|
||||||
- name: 3.同步文件到远程服务器
|
|
||||||
uses: appleboy/scp-action@v0.1.7
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
source: "./dist/*"
|
|
||||||
target: ${{ env.REMOTE_DEPLOY_PATH }}
|
|
||||||
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
||||||
strip_components: 0
|
|
||||||
|
|
||||||
- name: 4. 远程重载 Nginx 配置
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SERVER_HOST }}
|
|
||||||
username: ${{ secrets.SERVER_USER }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
# 核心:执行 Nginx 重载命令
|
|
||||||
script: |
|
|
||||||
echo "尝试重载 Nginx 服务..."
|
|
||||||
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
|
|
||||||
# 否则工作流可能会因为权限不足而失败。
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
echo "Nginx 重载命令已发送。"
|
|
||||||
|
|
||||||
- name: 5.发送构建结果邮件
|
|
||||||
if: always() # 无论上一步是否失败,都执行此步骤
|
|
||||||
uses: dawidd6/action-send-mail@v3
|
|
||||||
with:
|
|
||||||
|
|
||||||
from: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# --- 邮件配置 ---
|
|
||||||
server_address: smtp.gmail.com # 替换为你的SMTP服务器地址
|
|
||||||
server_port: 465 # 替换为你的SMTP端口 (通常是465或587)
|
|
||||||
username: ${{ secrets.MAIL_USERNAME }} # 存储在Secrets中的邮箱用户名
|
|
||||||
password: ${{ secrets.MAIL_PASSWORD }} # 存储在Secrets中的邮箱密码
|
|
||||||
subject: 'Gitea Actions 构建通知: ${{ job.status }} - AiDA back-java Develop'
|
|
||||||
# 收件人列表,可以根据需要更改
|
|
||||||
to: 'xupei3360@163.com,txli@aidlab.hk,cgzhou@aidlab.hk,zchengrong@yeah.net' # 替换为实际收件人邮箱
|
|
||||||
|
|
||||||
# --- 邮件正文内容 ---
|
|
||||||
body: |
|
|
||||||
项目: AiDA back-java Develop
|
|
||||||
分支: dev/3.1_release_merge
|
|
||||||
|
|
||||||
🎉 构建结果: ${{ job.status }}
|
|
||||||
|
|
||||||
📅 构建时间: ${{ steps.build_start_time.outputs.current_time }}
|
|
||||||
|
|
||||||
🔗 构建链接: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
|
|
||||||
|
|
||||||
# 确保邮件内容为纯文本,或者你可以设置为 html: true 并调整 body
|
|
||||||
content_type: text/plain
|
|
||||||
1
.gitignore
vendored
@@ -24,3 +24,4 @@ dist.rar
|
|||||||
*.sw?
|
*.sw?
|
||||||
.eslintrc-auto-import.json
|
.eslintrc-auto-import.json
|
||||||
components.d.ts
|
components.d.ts
|
||||||
|
.cursor
|
||||||
260
package-lock.json
generated
@@ -34,6 +34,7 @@
|
|||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^9.6.1",
|
"vue-i18n": "^9.6.1",
|
||||||
"vue-router": "^4.0.3",
|
"vue-router": "^4.0.3",
|
||||||
|
"vue3-moveable": "^0.28.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuex": "^4.0.0",
|
"vuex": "^4.0.0",
|
||||||
"x-sender": "^1.1.6"
|
"x-sender": "^1.1.6"
|
||||||
@@ -232,6 +233,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cfcs/core": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cfcs/core/-/core-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@egjs/component": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ctrl/tinycolor": {
|
"node_modules/@ctrl/tinycolor": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||||
@@ -240,6 +250,39 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@daybrush/utils": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@daybrush/utils/-/utils-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@egjs/agent": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@egjs/agent/-/agent-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-cvAPSlUILhBBOakn2krdPnOGv5hAZq92f1YHxYcfu0p7uarix2C6Ia3AVizpS1SGRZGiEkIS5E+IVTLg1I2Iog==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@egjs/children-differ": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@egjs/children-differ/-/children-differ-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@egjs/list-differ": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@egjs/component": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@egjs/component/-/component-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@egjs/list-differ": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@egjs/list-differ/-/list-differ-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@element-plus/icons-vue": {
|
"node_modules/@element-plus/icons-vue": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
||||||
@@ -1224,6 +1267,34 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@scena/dragscroll": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@scena/dragscroll/-/dragscroll-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.6.0",
|
||||||
|
"@scena/event-emitter": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scena/event-emitter": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@scena/event-emitter/-/event-emitter-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scena/matrix": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@scena/matrix/-/matrix-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@simonwep/pickr": {
|
"node_modules/@simonwep/pickr": {
|
||||||
"version": "1.8.2",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
|
||||||
@@ -2904,6 +2975,52 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/croact": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/croact/-/croact-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-9GhvyzTY/IVUrMQ2iz/mzgZ8+NcjczmIo/t4FkC1CU0CEcau6v6VsEih4jkTa4ZmRgYTF0qXEZLObCzdDFplpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@egjs/list-differ": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/croact-css-styled": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/croact-css-styled/-/croact-css-styled-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-G7yvRiVJ3Eoj0ov2h2xR4312hpOzATay2dGS9clK8yJQothjH1sBXIyvOeRP5wBKD9mPcKcoUXPCPsl0tQog4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"css-styled": "~1.0.8",
|
||||||
|
"framework-utils": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/croact-moveable": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/croact-moveable/-/croact-moveable-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-fc3bieV6CdqqZFtzsSLi9KmvUMFW3oakUfhPCls1BxKjOfUfn8rktteGED2341A/Qghy8tI3Hm6SdocIc68IKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@egjs/agent": "^2.2.1",
|
||||||
|
"@egjs/children-differ": "^1.0.1",
|
||||||
|
"@egjs/list-differ": "^1.0.0",
|
||||||
|
"@scena/dragscroll": "^1.4.0",
|
||||||
|
"@scena/event-emitter": "^1.0.5",
|
||||||
|
"@scena/matrix": "^1.1.1",
|
||||||
|
"croact-css-styled": "^1.1.9",
|
||||||
|
"css-to-mat": "^1.1.1",
|
||||||
|
"framework-utils": "^1.1.0",
|
||||||
|
"gesto": "^1.19.3",
|
||||||
|
"overlap-area": "^1.1.0",
|
||||||
|
"react-css-styled": "^1.1.9",
|
||||||
|
"react-moveable": "~0.56.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"croact": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -3014,6 +3131,25 @@
|
|||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-styled": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/css-styled/-/css-styled-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-to-mat": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/css-to-mat/-/css-to-mat-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@scena/matrix": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-tree": {
|
"node_modules/css-tree": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz",
|
||||||
@@ -4354,6 +4490,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framework-utils": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/framework-utils/-/framework-utils-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
@@ -4485,6 +4627,16 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gesto": {
|
||||||
|
"version": "1.19.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/gesto/-/gesto-1.19.4.tgz",
|
||||||
|
"integrity": "sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@scena/event-emitter": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -5695,6 +5847,24 @@
|
|||||||
"setimmediate": "^1.0.5"
|
"setimmediate": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/keycode": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/keycode/-/keycode-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/keycon": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/keycon/-/keycon-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@cfcs/core": "^0.0.6",
|
||||||
|
"@daybrush/utils": "^1.7.1",
|
||||||
|
"@scena/event-emitter": "^1.0.2",
|
||||||
|
"keycode": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
|
||||||
@@ -6212,6 +6382,19 @@
|
|||||||
"pathe": "^2.0.1"
|
"pathe": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moveable": {
|
||||||
|
"version": "0.53.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/moveable/-/moveable-0.53.0.tgz",
|
||||||
|
"integrity": "sha512-71jS9zIoQzMhnNvduhg4tUEdm23+fO/40FN7muVMbZvVwbTku2MIxxLhnU4qFvxI4oVxn75l79SbtgjuA+s7Pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@scena/event-emitter": "^1.0.5",
|
||||||
|
"croact": "^1.0.4",
|
||||||
|
"croact-moveable": "~0.9.0",
|
||||||
|
"react-moveable": "~0.56.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -6650,6 +6833,15 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/overlap-area": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/overlap-area/-/overlap-area-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/own-keys": {
|
"node_modules/own-keys": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz",
|
||||||
@@ -7037,6 +7229,46 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-css-styled": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-css-styled/-/react-css-styled-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-styled": "~1.0.8",
|
||||||
|
"framework-utils": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-moveable": {
|
||||||
|
"version": "0.56.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-moveable/-/react-moveable-0.56.0.tgz",
|
||||||
|
"integrity": "sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@egjs/agent": "^2.2.1",
|
||||||
|
"@egjs/children-differ": "^1.0.1",
|
||||||
|
"@egjs/list-differ": "^1.0.0",
|
||||||
|
"@scena/dragscroll": "^1.4.0",
|
||||||
|
"@scena/event-emitter": "^1.0.5",
|
||||||
|
"@scena/matrix": "^1.1.1",
|
||||||
|
"css-to-mat": "^1.1.1",
|
||||||
|
"framework-utils": "^1.1.0",
|
||||||
|
"gesto": "^1.19.3",
|
||||||
|
"overlap-area": "^1.1.0",
|
||||||
|
"react-css-styled": "^1.1.9",
|
||||||
|
"react-selecto": "^1.25.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-selecto": {
|
||||||
|
"version": "1.26.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-selecto/-/react-selecto-1.26.3.tgz",
|
||||||
|
"integrity": "sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"selecto": "~1.26.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
@@ -7507,6 +7739,24 @@
|
|||||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/selecto": {
|
||||||
|
"version": "1.26.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/selecto/-/selecto-1.26.3.tgz",
|
||||||
|
"integrity": "sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@daybrush/utils": "^1.13.0",
|
||||||
|
"@egjs/children-differ": "^1.0.1",
|
||||||
|
"@scena/dragscroll": "^1.4.0",
|
||||||
|
"@scena/event-emitter": "^1.0.5",
|
||||||
|
"css-styled": "^1.0.8",
|
||||||
|
"css-to-mat": "^1.1.1",
|
||||||
|
"framework-utils": "^1.1.0",
|
||||||
|
"gesto": "^1.19.4",
|
||||||
|
"keycon": "^1.2.0",
|
||||||
|
"overlap-area": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
|
||||||
@@ -9765,6 +10015,16 @@
|
|||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue3-moveable": {
|
||||||
|
"version": "0.28.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue3-moveable/-/vue3-moveable-0.28.0.tgz",
|
||||||
|
"integrity": "sha512-vplQO0XkxVEtXMDh2/lZE+c5kMycGXAfYFMvbwFKi8UVYzVk8MTgVHr4fxO9Z+4i4Rb+U/IEIgkhHRMAbx8FJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framework-utils": "^1.1.0",
|
||||||
|
"moveable": "~0.53.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vuedraggable": {
|
"node_modules/vuedraggable": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^9.6.1",
|
"vue-i18n": "^9.6.1",
|
||||||
"vue-router": "^4.0.3",
|
"vue-router": "^4.0.3",
|
||||||
|
"vue3-moveable": "^0.28.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuex": "^4.0.0",
|
"vuex": "^4.0.0",
|
||||||
"x-sender": "^1.1.6"
|
"x-sender": "^1.1.6"
|
||||||
|
|||||||
53
prod_build_manual.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: AiDA WEB-Node.js StableVersion 分支构建部署
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [ 18.18.0 ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 1.检出代码
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: StableVersion
|
||||||
|
|
||||||
|
- name: 2.打印当前分支信息
|
||||||
|
run: |
|
||||||
|
echo "Current branch being deployed is: $(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
echo "The code is from the 'main' branch, as specified in 'actions/checkout'."
|
||||||
|
|
||||||
|
- name: 3.设置 Node.js 环境 ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build
|
||||||
|
- run: ls -l
|
||||||
|
|
||||||
|
- name: 3.5. 手动安装 AWS CLI v2 # 新增步骤:确保 aws 命令可用
|
||||||
|
run: |
|
||||||
|
echo "安装 AWS CLI V2..."
|
||||||
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||||
|
unzip awscliv2.zip
|
||||||
|
sudo ./aws/install --update
|
||||||
|
aws --version
|
||||||
|
echo "AWS CLI V2 安装完成。"
|
||||||
|
|
||||||
|
- name: 4.配置 AWS 凭证
|
||||||
|
uses: aws-actions/configure-aws-credentials@main
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: 'ap-east-1'
|
||||||
|
|
||||||
|
- name: 5.同步 dist 目录到 S3
|
||||||
|
run: |
|
||||||
|
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_NAME }}/ --acl public-read
|
||||||
|
|
||||||
|
- name: 6.部署完成
|
||||||
|
run: echo "构建和部署到 S3 任务完成。"
|
||||||
BIN
public/image/toolsGuide/detailCN.png
Normal file
|
After Width: | Height: | Size: 243 KiB |
BIN
public/image/toolsGuide/detailEN.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
23
src/App.vue
@@ -1,7 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
|
<div class="loading" v-show="loading"><a-spin :delay="0.5" /></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
const store = useStore();
|
||||||
|
const loading = computed(() => store.state.loading || store.state.view_loading);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
@@ -9,7 +18,19 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.loading{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999999999999;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
.ipad{
|
.ipad{
|
||||||
*{
|
*{
|
||||||
-webkit-touch-callout:none;
|
-webkit-touch-callout:none;
|
||||||
|
|||||||
@@ -54,6 +54,24 @@
|
|||||||
<div class="content unicode" style="display: block;">
|
<div class="content unicode" style="display: block;">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">混合模式</div>
|
||||||
|
<div class="code-name">&#xe7a4;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">更多</div>
|
||||||
|
<div class="code-name">&#xe60f;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">平铺</div>
|
||||||
|
<div class="code-name">&#xe8d7;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont"></span>
|
<span class="icon iconfont"></span>
|
||||||
<div class="name">裁剪</div>
|
<div class="name">裁剪</div>
|
||||||
@@ -276,9 +294,9 @@
|
|||||||
<pre><code class="language-css"
|
<pre><code class="language-css"
|
||||||
>@font-face {
|
>@font-face {
|
||||||
font-family: 'iconfont';
|
font-family: 'iconfont';
|
||||||
src: url('iconfont.woff2?t=1762934152017') format('woff2'),
|
src: url('iconfont.woff2?t=1766460927921') format('woff2'),
|
||||||
url('iconfont.woff?t=1762934152017') format('woff'),
|
url('iconfont.woff?t=1766460927921') format('woff'),
|
||||||
url('iconfont.ttf?t=1762934152017') format('truetype');
|
url('iconfont.ttf?t=1766460927921') format('truetype');
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||||
@@ -304,6 +322,33 @@
|
|||||||
<div class="content font-class">
|
<div class="content font-class">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-hunhemoshi"></span>
|
||||||
|
<div class="name">
|
||||||
|
混合模式
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-hunhemoshi
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-gengduo"></span>
|
||||||
|
<div class="name">
|
||||||
|
更多
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-gengduo
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-repeat"></span>
|
||||||
|
<div class="name">
|
||||||
|
平铺
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-repeat
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont icon-caijian"></span>
|
<span class="icon iconfont icon-caijian"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
@@ -637,6 +682,30 @@
|
|||||||
<div class="content symbol">
|
<div class="content symbol">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-hunhemoshi"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">混合模式</div>
|
||||||
|
<div class="code-name">#icon-hunhemoshi</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-gengduo"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">更多</div>
|
||||||
|
<div class="code-name">#icon-gengduo</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-repeat"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">平铺</div>
|
||||||
|
<div class="code-name">#icon-repeat</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#icon-caijian"></use>
|
<use xlink:href="#icon-caijian"></use>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4292253 */
|
font-family: "iconfont"; /* Project id 4292253 */
|
||||||
src: url('iconfont.woff2?t=1762934152017') format('woff2'),
|
src: url('iconfont.woff2?t=1766460927921') format('woff2'),
|
||||||
url('iconfont.woff?t=1762934152017') format('woff'),
|
url('iconfont.woff?t=1766460927921') format('woff'),
|
||||||
url('iconfont.ttf?t=1762934152017') format('truetype');
|
url('iconfont.ttf?t=1766460927921') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,18 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-hunhemoshi:before {
|
||||||
|
content: "\e7a4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gengduo:before {
|
||||||
|
content: "\e60f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-repeat:before {
|
||||||
|
content: "\e8d7";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-caijian:before {
|
.icon-caijian:before {
|
||||||
content: "\e650";
|
content: "\e650";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,27 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "42604348",
|
||||||
|
"name": "混合模式",
|
||||||
|
"font_class": "hunhemoshi",
|
||||||
|
"unicode": "e7a4",
|
||||||
|
"unicode_decimal": 59300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45981931",
|
||||||
|
"name": "更多",
|
||||||
|
"font_class": "gengduo",
|
||||||
|
"unicode": "e60f",
|
||||||
|
"unicode_decimal": 58895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "17005660",
|
||||||
|
"name": "平铺",
|
||||||
|
"font_class": "repeat",
|
||||||
|
"unicode": "e8d7",
|
||||||
|
"unicode_decimal": 59607
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "22138606",
|
"icon_id": "22138606",
|
||||||
"name": "裁剪",
|
"name": "裁剪",
|
||||||
|
|||||||
17
src/assets/icons/CBrush2.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="201.000000pt" height="200.000000pt" viewBox="0 0 201.000000 200.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1654 1990 c-26 -8 -153 -110 -162 -129 -1 -3 81 -89 183 -191 l185
|
||||||
|
-185 56 55 c30 30 62 67 70 82 22 41 18 123 -8 176 -29 57 -126 158 -169 176
|
||||||
|
-47 20 -118 27 -155 16z"/>
|
||||||
|
<path d="M868 1243 c-525 -527 -498 -492 -568 -725 -54 -179 -59 -219 -30
|
||||||
|
-248 19 -19 29 -21 64 -16 113 18 361 105 431 152 28 18 251 235 498 481 l447
|
||||||
|
448 -188 188 -187 187 -467 -467z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 799 B |
17
src/assets/icons/CEraser2.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M757 1273 c-214 -214 -402 -406 -418 -427 -32 -43 -37 -82 -15 -124
|
||||||
|
23 -44 327 -338 376 -363 l45 -24 463 -3 462 -3 0 46 0 45 -342 0 -343 0 342
|
||||||
|
343 c187 188 346 352 352 364 16 32 13 74 -7 105 -9 14 -110 116 -224 227
|
||||||
|
l-207 201 -47 0 -48 0 -389 -387z m207 -342 l209 -209 -128 -131 c-76 -78
|
||||||
|
-144 -138 -169 -151 -51 -24 -106 -26 -149 -4 -41 20 -337 314 -337 334 0 13
|
||||||
|
347 370 360 370 3 0 99 -94 214 -209z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 847 B |
20
src/assets/icons/CMarquee.svg
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M250 1545 l0 -205 80 0 80 0 0 -340 0 -340 -80 0 -80 0 0 -205 0
|
||||||
|
-205 205 0 205 0 0 80 0 80 340 0 340 0 0 -80 0 -80 205 0 205 0 0 205 0 205
|
||||||
|
-80 0 -80 0 0 340 0 340 80 0 80 0 0 205 0 205 -205 0 -205 0 0 -80 0 -80
|
||||||
|
-340 0 -340 0 0 80 0 80 -205 0 -205 0 0 -205z m320 0 l0 -125 -120 0 -120 0
|
||||||
|
0 125 0 125 120 0 120 0 0 -125z m1100 0 l0 -125 -120 0 -120 0 0 125 0 125
|
||||||
|
120 0 120 0 0 -125z m-330 -125 l0 -80 85 0 85 0 0 -340 0 -340 -85 0 -85 0 0
|
||||||
|
-80 0 -80 -340 0 -340 0 0 80 0 80 -85 0 -85 0 0 340 0 340 85 0 85 0 0 80 0
|
||||||
|
80 340 0 340 0 0 -80z m-770 -965 l0 -125 -120 0 -120 0 0 125 0 125 120 0
|
||||||
|
120 0 0 -125z m1100 0 l0 -125 -120 0 -120 0 0 125 0 125 120 0 120 0 0 -125z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
68
src/assets/icons/CPart.svg
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="103.000000pt" height="92.000000pt" viewBox="0 0 103.000000 92.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,92.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M365 895 c-5 -2 -36 -6 -67 -10 -45 -5 -58 -10 -59 -23 0 -11 -2 -12
|
||||||
|
-6 -4 -7 17 -32 15 -40 -4 -4 -11 -8 -12 -13 -4 -5 8 -13 9 -21 4 -7 -4 -22
|
||||||
|
-9 -34 -10 -11 -1 -41 -11 -66 -21 l-47 -18 20 -63 c11 -38 23 -60 30 -56 6 4
|
||||||
|
8 -1 3 -15 -3 -11 -3 -21 2 -21 4 0 9 -12 9 -26 1 -14 5 -28 8 -32 4 -3 33 3
|
||||||
|
66 15 33 11 61 19 62 18 2 -2 1 -139 -2 -304 l-5 -301 309 2 309 3 -2 108 c-2
|
||||||
|
77 2 113 11 125 10 12 10 14 1 8 -7 -4 -13 -2 -13 4 0 6 10 8 23 5 18 -5 26
|
||||||
|
-1 36 17 9 17 10 18 6 3 -5 -16 -4 -18 6 -7 18 17 -1 34 -40 37 l-32 2 -2 151
|
||||||
|
c-1 82 0 147 3 144 9 -12 44 -11 52 1 5 8 8 7 8 -4 0 -14 26 -28 52 -29 11 0
|
||||||
|
39 70 33 80 -3 5 1 11 7 13 10 4 9 8 -2 16 -13 10 -8 15 12 12 8 -1 38 91 32
|
||||||
|
98 -2 2 -10 -2 -18 -8 -9 -7 -17 -8 -20 -2 -3 5 0 11 6 14 7 2 -18 14 -56 26
|
||||||
|
-38 12 -71 19 -74 16 -3 -3 -11 0 -18 6 -8 6 -20 9 -28 6 -8 -3 -16 -2 -18 3
|
||||||
|
-5 15 -154 30 -286 29 -70 0 -131 -2 -137 -4z m21 -30 c-6 -18 3 -47 14 -40 4
|
||||||
|
2 18 -7 30 -20 27 -29 17 -34 -14 -7 -20 16 -20 16 -7 -1 23 -29 66 -47 112
|
||||||
|
-47 34 0 40 3 35 16 -5 14 -4 15 9 4 13 -11 19 -9 36 6 12 11 29 35 38 54 15
|
||||||
|
31 20 35 58 35 24 0 43 -2 43 -5 0 -11 115 -27 121 -18 3 5 9 2 13 -7 4 -13
|
||||||
|
14 -16 34 -12 15 2 36 1 47 -4 16 -7 14 -8 -10 -5 l-30 5 32 -14 c39 -18 39
|
||||||
|
-19 14 -83 -15 -38 -17 -52 -8 -61 9 -9 8 -11 -7 -5 -16 6 -18 4 -12 -19 3
|
||||||
|
-14 2 -29 -4 -32 -6 -3 -7 1 -4 9 6 16 -9 20 -73 19 -12 0 -20 4 -17 8 3 5 -4
|
||||||
|
9 -15 9 -20 0 -21 -5 -21 -151 l0 -151 -27 8 c-16 4 -38 7 -50 7 -19 -1 -21 2
|
||||||
|
-13 17 15 28 12 57 -6 57 -11 0 -15 -8 -12 -27 4 -24 1 -27 -23 -26 -15 1 -25
|
||||||
|
4 -22 9 2 4 -2 7 -10 7 -9 0 -14 -10 -14 -25 0 -13 3 -22 8 -19 5 3 6 -1 3 -9
|
||||||
|
-2 -7 0 -25 5 -40 8 -20 7 -25 -3 -21 -7 3 -13 -2 -13 -11 0 -12 7 -15 26 -11
|
||||||
|
14 3 21 3 14 0 -7 -3 -9 -12 -6 -20 3 -7 10 -11 16 -7 5 3 7 1 4 -4 -9 -14 3
|
||||||
|
-74 12 -68 4 2 7 -6 7 -18 -1 -32 32 -34 44 -3 5 14 7 34 5 46 -3 13 0 18 7
|
||||||
|
14 8 -5 9 -1 5 10 -4 9 -3 15 2 12 5 -3 12 1 15 10 3 8 2 12 -4 9 -6 -3 -10
|
||||||
|
-1 -10 4 0 13 3 13 24 5 13 -5 16 -24 16 -103 0 -63 4 -102 13 -111 10 -12 9
|
||||||
|
-12 -4 -2 -13 10 -88 12 -300 10 l-284 -3 3 303 3 302 -26 0 c-14 0 -25 -4
|
||||||
|
-25 -10 0 -5 -7 -6 -17 -3 -9 4 -14 2 -10 -3 4 -6 -6 -9 -24 -6 -28 4 -41 -11
|
||||||
|
-19 -21 6 -3 5 -4 -2 -3 -7 2 -13 11 -13 22 -1 32 -25 104 -35 104 -5 0 -6 7
|
||||||
|
-3 17 4 10 2 14 -5 9 -7 -4 -10 2 -8 17 3 29 14 47 29 47 7 0 3 -8 -8 -17
|
||||||
|
l-20 -16 20 8 c11 4 28 10 37 12 10 3 18 9 18 13 0 4 6 7 12 7 22 -2 158 26
|
||||||
|
158 32 0 3 20 6 45 5 25 0 45 3 45 8 0 4 3 8 6 8 3 0 4 -7 0 -15z m237 -5 c-3
|
||||||
|
-9 1 -8 11 4 9 11 16 15 16 9 0 -6 -7 -16 -15 -23 -8 -7 -15 -9 -15 -4 0 10
|
||||||
|
-29 -28 -30 -40 0 -4 8 -5 17 -2 15 6 15 4 -2 -14 -22 -24 -37 -26 -28 -4 5
|
||||||
|
14 3 15 -15 5 -26 -14 -77 -14 -103 -1 -10 6 -27 27 -38 48 l-19 37 113 0 c95
|
||||||
|
0 112 -2 108 -15z"/>
|
||||||
|
<path d="M346 641 c-3 -5 1 -12 10 -15 23 -9 36 -7 29 4 -3 6 1 7 9 4 9 -3 16
|
||||||
|
-1 16 5 0 13 -56 15 -64 2z"/>
|
||||||
|
<path d="M440 640 c0 -16 33 -26 38 -12 2 7 8 10 13 6 5 -3 9 0 9 5 0 6 -13
|
||||||
|
11 -30 11 -16 0 -30 -5 -30 -10z"/>
|
||||||
|
<path d="M530 641 c0 -12 37 -24 50 -16 20 12 10 25 -20 25 -16 0 -30 -4 -30
|
||||||
|
-9z"/>
|
||||||
|
<path d="M620 641 c0 -12 37 -24 50 -16 20 12 10 25 -20 25 -16 0 -30 -4 -30
|
||||||
|
-9z"/>
|
||||||
|
<path d="M310 593 c0 -20 5 -30 16 -30 10 0 14 8 12 25 -4 37 -28 40 -28 5z"/>
|
||||||
|
<path d="M697 613 c-13 -13 -7 -50 8 -50 10 0 15 10 15 29 0 27 -9 35 -23 21z"/>
|
||||||
|
<path d="M317 534 c-4 -4 -7 -20 -7 -36 0 -35 23 -34 28 1 4 25 -10 46 -21 35z"/>
|
||||||
|
<path d="M692 503 c4 -39 28 -42 28 -4 0 21 -5 31 -16 31 -11 0 -14 -8 -12
|
||||||
|
-27z"/>
|
||||||
|
<path d="M312 415 c4 -33 22 -33 26 0 2 18 -1 25 -13 25 -12 0 -15 -7 -13 -25z"/>
|
||||||
|
<path d="M312 329 c2 -19 8 -33 13 -31 15 3 12 55 -3 60 -10 3 -13 -5 -10 -29z"/>
|
||||||
|
<path d="M342 278 c3 -7 19 -14 37 -16 24 -3 32 0 29 10 -3 7 -19 14 -37 16
|
||||||
|
-24 3 -32 0 -29 -10z"/>
|
||||||
|
<path d="M443 275 c0 -10 10 -15 29 -15 18 0 28 5 28 15 0 10 -10 15 -28 15
|
||||||
|
-19 0 -29 -5 -29 -15z"/>
|
||||||
|
<path d="M530 275 c0 -10 10 -15 33 -15 22 0 28 3 18 9 -11 7 -11 9 0 14 8 3
|
||||||
|
-1 6 -18 6 -23 1 -33 -4 -33 -14z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
31
src/assets/icons/CPoint.svg
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="96.000000pt" height="96.000000pt" viewBox="0 0 96.000000 96.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,96.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M346 936 c-21 -13 -49 -41 -62 -62 -41 -67 -27 -180 27 -218 47 -32
|
||||||
|
53 -12 12 40 -71 94 -2 229 116 229 37 0 58 -7 87 -28 32 -22 40 -24 42 -12
|
||||||
|
14 64 -142 100 -222 51z"/>
|
||||||
|
<path d="M368 877 c-33 -28 -48 -57 -48 -96 0 -39 9 -61 26 -61 10 0 14 13 14
|
||||||
|
46 0 57 12 79 50 93 61 21 110 -22 110 -96 0 -48 14 -56 31 -19 31 67 -35 156
|
||||||
|
-114 156 -29 0 -50 -7 -69 -23z"/>
|
||||||
|
<path d="M580 794 c0 -58 -9 -84 -43 -121 -19 -21 -19 -23 -2 -29 34 -13 85
|
||||||
|
75 85 148 0 37 -10 58 -26 58 -10 0 -14 -15 -14 -56z"/>
|
||||||
|
<path d="M410 749 c-13 -6 -28 -15 -32 -22 -4 -7 -8 -106 -8 -222 l-1 -210
|
||||||
|
-27 34 c-62 80 -89 101 -126 101 -27 0 -39 -6 -52 -25 -15 -24 -15 -28 0 -68
|
||||||
|
21 -53 78 -123 94 -113 18 11 16 17 -28 75 -44 58 -48 72 -25 91 21 18 54 -6
|
||||||
|
93 -68 31 -47 74 -78 93 -67 5 4 9 101 9 224 0 225 3 241 42 241 10 0 19 -1
|
||||||
|
19 -2 1 -2 5 -86 8 -188 5 -165 8 -185 24 -188 15 -3 17 5 17 61 0 72 17 100
|
||||||
|
45 77 10 -9 15 -32 15 -77 0 -56 2 -64 18 -61 12 2 16 11 14 34 -5 48 14 86
|
||||||
|
41 82 20 -3 22 -9 25 -66 3 -53 6 -63 20 -60 12 2 19 16 22 43 4 32 10 41 27
|
||||||
|
43 36 5 46 -36 39 -166 -6 -127 -19 -160 -75 -195 -48 -29 -176 -34 -246 -10
|
||||||
|
-48 18 -56 25 -130 128 -16 22 -25 26 -37 19 -15 -8 -11 -18 31 -75 78 -105
|
||||||
|
94 -113 236 -117 119 -3 121 -3 167 27 69 44 82 74 86 214 6 179 -8 221 -72
|
||||||
|
216 -22 -2 -39 4 -55 20 -17 17 -32 22 -55 19 -19 -2 -38 2 -45 9 -6 7 -25 13
|
||||||
|
-40 13 l-29 0 -4 100 c-3 103 -11 122 -53 133 -11 3 -31 1 -45 -4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
9
src/assets/icons/overallMore.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<rect width="24" height="24" fill="url(#pattern0_2641_12790)"/>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0_2641_12790" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_2641_12790" transform="scale(0.0078125)"/>
|
||||||
|
</pattern>
|
||||||
|
<image id="image0_2641_12790" width="128" height="128" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAACIlJREFUeJztnWlsFVUUgL9OyxNwoVUoCBZFVNxQUaNWo0aiica4SxTUGJe4x+0PBJc/rlH8oSYucQvgQkARixpCECNqgtGKiiigssbK5sLeCvT54/TR+mz75s7cuXfeeL7k/Gpn3jlzZ+5y7rnngKIo/18qfCuQcfYGBgF922QzsA5YD6zxqJeSEBXAWcAEoBHYBeS7kFXAROAaoLcPZRV75IAbgYV03eDdye/AY8BA14or8akHFhGt4YtlKzAWqHRqgRKJAHiC7rv5qPIZsL87UxRT9gCmYL/hO8py4HBXBinhyQFzSbbxC7IBONKNWUpYXsRN43fsCfo6sUwpya24bfyCzEZ9Nt7pD2zEzwuQB65N3kSlOybhr/HziOewj22jdL0ZjiOQsd+0G94MvAe8grxAHwM/A7VAjeG99gK2AJ8aXqdYYAJmX+tO4HGguov7VQBjgCbD+y5D/A+KQ3oAawnfSFuBc0PeeyCwwODeeeAcCzZ1yjHAm8hY43OsK3cZZfjcB2LeE0SV34A3gOHFSowBWlLw8Mpdphc/2JBc5VjPFmB04cePQRvflpxENAJghWNdW4DhATAOcW8q8WgCvox4bSsw06IuYcgB4wJgpOMfzipLkS8rKottKWLAyICulyqKGZtjXr/RihZmVBfGHiU+tTGvH2BFCzOWB8BUDz+cRYYTL7bvFFuKGDANxL/8M/5n0FmQ3UsrQ2qQIcSlrj8B+xQUGIK5R0rlv7KEaCuqpxzr2Yi0+b82NyqBC5CwZo1HaycHXGTw/68gEcJhOR9oILyPvxHZE4jCb8iGVAOy9FRC0ojZF/Yy4XqC0cA2g/u2AodYskkx4DbMu9mliIu9eGIYIBO+hgj3nJuQfUoJqoE/iDbebgPmI3EB85BuOOrYfWnShipdcyd+JpYF+RSNC/RKFfAdfhp/J3Bs8iYqpTgWCcty/QKMdWGcEo5LkNm4q8afinb9qeMukjkTWCwfAr0c2aQYcimwneQafxISj6ikmFOBH7Hb8FuQHka7/TKhJ/AwdnqDD4CDnGqvWKMfMltfjVmjtyATvZPdq6zdTBJUAscjm2qnA4ORl6MvsAlJELUW+ArZmJlH/GgiRVEURVEURQlPGlYBVciRqhOR2XItkmJ1AzJjXo1shf7iS0HFPgESZ/c+4aNhVwLPoVmzypoAuIl4YeitwCykx1DKiKHAR9jzme9C0rbs5dIIJRqXYRb9aiKLEG+bklLuIPm98ybgOFcGKeG5iWQbvqNsoO20i5IOTgWacfcC5IEf6HDeTfFHDf6STU12YJ9Sgmfx0/gFOTN5E5WuOBrYgd8X4Gs0oaI3XsO8wf4CngROQ1zBOaAOybk3nWhh2GGTNSoW2QfzgxNTgP1K3PckZC/A5L5v2zMruxRvBvVCXKxR89XUA/cY/P/Tbf+fD/G/fYFPCL8X8DdwHTIcKcIaJBRte/EfKoEHcZumZA7m2cqH4udoVpZkE/BAx2dfAbzlWIlddJKvNiQPpeAhZkHepG0EcJ2nNk+8nPd1uD2bl2UZEwA3mz1/K8yJce1q/GTVzCI3B8AIDz+8Mub1q6xooYzw5SyJ+7tpCGXLAvkAyQ/omjrP1yvCggB43sMPxyl7MhgtqWqLF0C60zdwO/vchRSqiMLDjnXNqrxOh6G0ErgfcRK4UmAu5o6gQ5GiTL4fXjnLRmB84dkXT6Z6Iq7gqKliTV3BzwB3tylWCnUFxyNPuyu4OakfibIZNJXSxZHrkfy4JvedZs8sxYRXidYtTUDO0/dHtoMHA1cAM9Dt4LLiKPwHhDSiASFeeRp/jd8KnJG8iUp31BAvKXIcmejAPiUE9bgPC/8G2NOFcUo4bsBd469DU6ylkutJflL4K5pNO9VcTHKHQxcCB7gzRYnKwUggiK2G34GsNnTMLyMqkCGhUGc3irQiKVV9BLIolgiQEnUNhN+AWo4cORvmQd/MkobImipkA+pE5FRQLbKnsL5NVgKfIS+AoiiKoiiKoihxScMqIGtUASfQXjCiDol46kd7wYh1SGjWXKRgxCYvmipWqSVeyZh69yorNugJPIadre9ZiMtcKRNOA5Zgd3NrKxJZrcNzyrmcZAtHTkaCY5UUci9uchTMBno7skkJyWW4TVDxDjocpIbj8JOjaLwL45TuqQK+w33j55HDtFogwzOFdHa+5HMSGApMT+f+X6kG3kXW/KZsQ04qNSIewB5IUSxT6oDvkYzoimNux/yLXQxcyX9n8RVI5tMZEe75cUL2KSX4GrOGeolwa/hRmOU7aEVyJFij45hSiZRyOws5oasIOeS5hOUlpFJKWM4DZhJ+OG5EjspHYS2yAdWATCx3MwQ5VuVzkpMFWUw0792TjvVcQIfyOtWYZ+JW6VyuJBo1uM3TnEdqN/Yp5AYy6eKUztkG3Ei0lDTNwPG4rYq6L7AjQDY1lPgsRF6CqHxhSxEDRgXoiVpbrI15/RorWpgxJEDKtSjxiVuuro8VLcz4M0Dq+SrxGUY8V62P7KcfgRRuaMH/DDoLcrLR428nAFY41rUZqfAGwGj0JbAhM4jG1Y71bKaTJetwJGdwk6eHlxW5ovjBlmAQ7hJqNSE5gnd/+UppeiAz/LAPeSvi3g3DIMw9sGdbsEkxZAJmjbQTce/WdHG/AOn2Tb/8ZVhOgKmxZuE4AliE+fPagsT6z0fW+TXAYcCFwIER9LgPeDTCdYoFJuJ3brGG+L4GJQb9EaeZrxfgmuRNVEpxC34afxY6XKeGF3Db+MsoXU9BcUgOu/kOu5N1yARUSRk5kq+3vBxNiZdqAuBxZM1vu/HnAQPcmaLEYQTmUcNdyRYkyYRWOykzckiFsm+J1vDrgUfQrz4TnAk8AXxJ98PDCuA14Cqglw9FQdeWSbMnksq+H7KU24TM7AuiKIrikX8A+4ThOTuVZbQAAAAASUVORK5CYII="/>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/images/award/arrow.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
src/assets/images/award/bloom_bg.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/images/award/bloom_logo.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/images/award/code_create_logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/images/award/design_bg.png
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
BIN
src/assets/images/award/timeline_bg.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
src/assets/images/award/timeline_line.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/assets/images/award/∞.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
src/assets/images/canvas/add.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/assets/images/canvas/remove.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/images/canvas/shubiao-l.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/images/canvas/shubiao-r.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/canvas/xiangao.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
src/assets/images/canvas/xiangaofenge.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
src/assets/images/canvas/yinhua1.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
src/assets/images/icon/xyz.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 313 KiB |
9
src/assets/images/socialMediaLogo/biliBliIcon.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
10
src/assets/images/socialMediaLogo/faceBookIcon.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2427_2565)">
|
||||||
|
<path d="M12 0C5.37264 0 0 5.37264 0 12C0 17.6275 3.87456 22.3498 9.10128 23.6467V15.6672H6.62688V12H9.10128V10.4198C9.10128 6.33552 10.9498 4.4424 14.9597 4.4424C15.72 4.4424 17.0318 4.59168 17.5685 4.74048V8.06448C17.2853 8.03472 16.7933 8.01984 16.1822 8.01984C14.2147 8.01984 13.4544 8.76528 13.4544 10.703V12H17.3741L16.7006 15.6672H13.4544V23.9122C19.3963 23.1946 24.0005 18.1354 24.0005 12C24 5.37264 18.6274 0 12 0Z" fill="#2C2C2C"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2427_2565">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 691 B |
|
Before Width: | Height: | Size: 370 KiB |
10
src/assets/images/socialMediaLogo/linkedinIcon.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2427_2564)">
|
||||||
|
<path d="M22.2234 0H1.77187C0.792187 0 0 0.773438 0 1.72969V22.2656C0 23.2219 0.792187 24 1.77187 24H22.2234C23.2031 24 24 23.2219 24 22.2703V1.72969C24 0.773438 23.2031 0 22.2234 0ZM7.12031 20.4516H3.55781V8.99531H7.12031V20.4516ZM5.33906 7.43438C4.19531 7.43438 3.27188 6.51094 3.27188 5.37187C3.27188 4.23281 4.19531 3.30937 5.33906 3.30937C6.47813 3.30937 7.40156 4.23281 7.40156 5.37187C7.40156 6.50625 6.47813 7.43438 5.33906 7.43438ZM20.4516 20.4516H16.8937V14.8828C16.8937 13.5563 16.8703 11.8453 15.0422 11.8453C13.1906 11.8453 12.9094 13.2938 12.9094 14.7891V20.4516H9.35625V8.99531H12.7687V10.5609H12.8156C13.2891 9.66094 14.4516 8.70938 16.1813 8.70938C19.7859 8.70938 20.4516 11.0813 20.4516 14.1656V20.4516V20.4516Z" fill="#2C2C2C"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2427_2564">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 997 B |
10
src/assets/images/socialMediaLogo/socialIcons.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2426_2350)">
|
||||||
|
<path d="M23.5233 7.12823C23.5233 7.12823 23.2913 5.49009 22.5766 4.77079C21.6717 3.8241 20.6601 3.81946 20.196 3.76377C16.8733 3.52246 11.8846 3.52246 11.8846 3.52246H11.8754C11.8754 3.52246 6.88669 3.52246 3.564 3.76377C3.09994 3.81946 2.08828 3.8241 1.18336 4.77079C0.468703 5.49009 0.241312 7.12823 0.241312 7.12823C0.241312 7.12823 0 9.05409 0 10.9753V12.7759C0 14.6971 0.236672 16.6229 0.236672 16.6229C0.236672 16.6229 0.468703 18.2611 1.17872 18.9804C2.08364 19.9271 3.27164 19.8946 3.80067 19.9967C5.70333 20.1777 11.88 20.2334 11.88 20.2334C11.88 20.2334 16.8733 20.2241 20.196 19.9874C20.6601 19.9317 21.6717 19.9271 22.5766 18.9804C23.2913 18.2611 23.5233 16.6229 23.5233 16.6229C23.5233 16.6229 23.76 14.7017 23.76 12.7759V10.9753C23.76 9.05409 23.5233 7.12823 23.5233 7.12823ZM9.42511 14.9616V8.28374L15.8431 11.6343L9.42511 14.9616Z" fill="#2C2C2C"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2426_2350">
|
||||||
|
<rect width="23.76" height="23.76" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 68 KiB |
3
src/assets/images/socialMediaLogo/tikTokIcon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="21" height="24" viewBox="0 0 21 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.9218 0H10.9175V16.1843C10.9175 18.1127 9.37747 19.6967 7.4609 19.6967C5.54433 19.6967 4.00424 18.1127 4.00424 16.1843C4.00424 14.2905 5.51011 12.7408 7.35825 12.672V8.60871C3.28553 8.67755 0 12.0177 0 16.1843C0 20.3854 3.35398 23.76 7.49514 23.76C11.6362 23.76 14.9902 20.351 14.9902 16.1843V7.88555C16.4961 8.98748 18.3442 9.64174 20.295 9.67619V5.61287C17.2833 5.50957 14.9218 3.03026 14.9218 0Z" fill="#2C2C2C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 532 B |
|
Before Width: | Height: | Size: 191 KiB |
9
src/assets/images/socialMediaLogo/xiaoHongShuIcon.svg
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 299 KiB |
@@ -1250,10 +1250,14 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
|
|||||||
background: #000 !important;
|
background: #000 !important;
|
||||||
border-color: #000 !important;
|
border-color: #000 !important;
|
||||||
}
|
}
|
||||||
|
.ant-spin .ant-spin-dot {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
.ant-spin-dot-item {
|
.ant-spin-dot-item {
|
||||||
background-color: #000000 !important;
|
background-color: #000000 !important;
|
||||||
width: 9px !important;
|
width: 0.9em !important;
|
||||||
height: 9px !important;
|
height: 0.9em !important;
|
||||||
}
|
}
|
||||||
.ant-spin {
|
.ant-spin {
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -1358,7 +1362,7 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
|
|||||||
}
|
}
|
||||||
.admin_page .admin_state_item > span {
|
.admin_page .admin_state_item > span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 13rem;
|
min-width: 13rem;
|
||||||
}
|
}
|
||||||
.admin_page .admin_state_item > span > span {
|
.admin_page .admin_state_item > span > span {
|
||||||
color: red;
|
color: red;
|
||||||
|
|||||||
@@ -1378,10 +1378,14 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//loding样式
|
//loding样式
|
||||||
|
.ant-spin .ant-spin-dot{
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
.ant-spin-dot-item{
|
.ant-spin-dot-item{
|
||||||
background-color: #000000 !important;
|
background-color: #000000 !important;
|
||||||
width: 9px !important;
|
width: .9em !important;
|
||||||
height: 9px !important;
|
height: .9em !important;
|
||||||
}
|
}
|
||||||
.ant-spin{
|
.ant-spin{
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -1490,7 +1494,7 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
>span{
|
>span{
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 13rem;
|
min-width: 13rem;
|
||||||
>span{
|
>span{
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
<div class="generalModel_btn">
|
<div class="generalModel_btn">
|
||||||
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
|
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
|
||||||
<svg
|
<svg
|
||||||
width="100%" height="100%"
|
width="100%"
|
||||||
|
height="100%"
|
||||||
viewBox="0 0 46 46"
|
viewBox="0 0 46 46"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -49,7 +50,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="allUserPoeration_center admin_page">
|
<div class="allUserPoeration_center admin_page">
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>{{ $t('admin.UserName') }}: <span>*</span></span>
|
<span>
|
||||||
|
{{ $t('admin.UserName') }}:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="userName"
|
v-model="userName"
|
||||||
:placeholder="$t('admin.enterUserName')"
|
:placeholder="$t('admin.enterUserName')"
|
||||||
@@ -58,7 +62,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>{{ $t('admin.UserEmail') }}: <span>*</span></span>
|
<span>
|
||||||
|
{{ $t('admin.UserEmail') }}:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="userEmail"
|
v-model="userEmail"
|
||||||
:placeholder="$t('admin.enterEmail')"
|
:placeholder="$t('admin.enterEmail')"
|
||||||
@@ -67,7 +74,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>{{ $t('admin.Password') }}: <span>*</span></span>
|
<span>
|
||||||
|
{{ $t('admin.Password') }}:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
@focus="focus"
|
@focus="focus"
|
||||||
@blur="blur"
|
@blur="blur"
|
||||||
@@ -86,6 +96,19 @@
|
|||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div class="admin_state_item" v-if="title?.value == 'Edit'">
|
||||||
|
<span>
|
||||||
|
{{ $t('admin.SubscribePlan') }}:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
|
<a-select
|
||||||
|
v-model:value="subscriptionPlanId"
|
||||||
|
style="width: 250px"
|
||||||
|
:options="activePlanOptions"
|
||||||
|
:field-names="{ label: 'name', value: 'id' }"
|
||||||
|
:placeholder="$t('admin.SelectPlan')"
|
||||||
|
></a-select>
|
||||||
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="allUserPoeration_btn admin_page">
|
<div class="allUserPoeration_btn admin_page">
|
||||||
<div class="admin_search_item" @click="cancelDsign">{{ $t('admin.Close') }}</div>
|
<div class="admin_search_item" @click="cancelDsign">{{ $t('admin.Close') }}</div>
|
||||||
@@ -96,7 +119,7 @@
|
|||||||
<a-spin size="large" />
|
<a-spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
ref,
|
ref,
|
||||||
@@ -105,90 +128,114 @@ import {
|
|||||||
onMounted,
|
onMounted,
|
||||||
nextTick,
|
nextTick,
|
||||||
toRefs,
|
toRefs,
|
||||||
} from "vue";
|
computed
|
||||||
import { Https } from "@/tool/https";
|
} from 'vue'
|
||||||
import { Modal, message } from "ant-design-vue";
|
import { Https } from '@/tool/https'
|
||||||
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
import { Modal, message } from 'ant-design-vue'
|
||||||
import { formatTime, isEmail } from "@/tool/util";
|
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||||
import md5 from "md5";
|
import { formatTime, isEmail } from '@/tool/util'
|
||||||
|
import md5 from 'md5'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {},
|
||||||
emits: ["searchHistoryList"],
|
props: {
|
||||||
|
planOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['searchHistoryList'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const {t} = useI18n()
|
const { t } = useI18n()
|
||||||
|
const { planOptions } = toRefs(props)
|
||||||
|
// 筛选出状态为 ACTIVE 的订阅计划
|
||||||
|
const activePlanOptions = computed(() => {
|
||||||
|
if (!planOptions.value || !Array.isArray(planOptions.value)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return planOptions.value.filter((plan: any) => plan.status === 'ACTIVE')
|
||||||
|
})
|
||||||
let operations = reactive({
|
let operations = reactive({
|
||||||
operationsModal: false,
|
operationsModal: false,
|
||||||
operationsEdit: false,
|
operationsEdit: false,
|
||||||
loadingShow: false,
|
loadingShow: false,
|
||||||
title: null,
|
title: null
|
||||||
});
|
})
|
||||||
let operationsData = reactive({
|
let operationsData = reactive({
|
||||||
accountId: -1,
|
accountId: -1,
|
||||||
userName: "",
|
userName: '',
|
||||||
userEmail: "",
|
userEmail: '',
|
||||||
password: "",
|
password: '',
|
||||||
oldPassword: "",
|
oldPassword: '',
|
||||||
credits: "",
|
credits: '',
|
||||||
});
|
subscriptionPlanId: '',
|
||||||
|
oldSubscriptionPlanId: ''
|
||||||
|
})
|
||||||
let state = ref([
|
let state = ref([
|
||||||
{
|
{
|
||||||
label: "visitor",
|
label: 'visitor',
|
||||||
value: "0",
|
value: '0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "yearly",
|
label: 'yearly',
|
||||||
value: "1",
|
value: '1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "monthly",
|
label: 'monthly',
|
||||||
value: "2",
|
value: '2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "trial",
|
label: 'trial',
|
||||||
value: "3",
|
value: '3'
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
let init = (funStr, data) => {
|
let init = (funStr, data) => {
|
||||||
operations.operationsModal = true;
|
operations.operationsModal = true
|
||||||
operations.operationsEdit = true;
|
operations.operationsEdit = true
|
||||||
operations.title = funStr;
|
operations.title = funStr
|
||||||
if (funStr.value == "Add") operations.operationsEdit = false;
|
if (funStr.value == 'Add') operations.operationsEdit = false
|
||||||
if (funStr.value == "Edit") {
|
if (funStr.value == 'Edit') {
|
||||||
operationsData.accountId = data.id;
|
operationsData.accountId = data.id
|
||||||
operationsData.userName = data.userName;
|
operationsData.userName = data.userName
|
||||||
operationsData.userEmail = data.userEmail;
|
operationsData.userEmail = data.userEmail
|
||||||
operationsData.password = data.userPassword?data.userPassword:null;
|
operationsData.password = data.userPassword ? data.userPassword : null
|
||||||
operationsData.oldPassword = data.userPassword;
|
operationsData.oldPassword = data.userPassword
|
||||||
// operationsData.validStartTime='2024-08-05T00:00:06'
|
// operationsData.validStartTime='2024-08-05T00:00:06'
|
||||||
// operationsData.validEndTime='2024-08-05T00:00:06'
|
// operationsData.validEndTime='2024-08-05T00:00:06'
|
||||||
operationsData.credits = data.creditsUsageLimit;
|
operationsData.credits = data.creditsUsageLimit
|
||||||
|
operationsData.subscriptionPlanId = data.subscriptionPlanId || ''
|
||||||
|
operationsData.oldSubscriptionPlanId = data.subscriptionPlanId || ''
|
||||||
// operationsData.accountId = data.accountId
|
// operationsData.accountId = data.accountId
|
||||||
// operationsData.userName = data.userName
|
// operationsData.userName = data.userName
|
||||||
// operationsData.userEmail = data.userEmail
|
// operationsData.userEmail = data.userEmail
|
||||||
// operationsData.validStartTime = formatTime(data.validStartTime)
|
// operationsData.validStartTime = formatTime(data.validStartTime)
|
||||||
// operationsData.validEndTime = formatTime(data.validEndTime)
|
// operationsData.validEndTime = formatTime(data.validEndTime)
|
||||||
}
|
}
|
||||||
};
|
if (funStr.value == 'Add') {
|
||||||
let focus = (event) => {
|
operationsData.subscriptionPlanId = ''
|
||||||
|
operationsData.oldSubscriptionPlanId = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let focus = event => {
|
||||||
if (operationsData.password == operationsData.oldPassword) {
|
if (operationsData.password == operationsData.oldPassword) {
|
||||||
operationsData.password = "";
|
operationsData.password = ''
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let blur = (event) => {
|
let blur = event => {
|
||||||
console.log(operationsData.password == "" && operationsData.oldPassword);
|
console.log(operationsData.password == '' && operationsData.oldPassword)
|
||||||
if (operationsData.password == "" && operationsData.oldPassword) {
|
if (operationsData.password == '' && operationsData.oldPassword) {
|
||||||
operationsData.password = operationsData.oldPassword;
|
operationsData.password = operationsData.oldPassword
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let setAddData = () => {
|
let setAddData = () => {
|
||||||
return {
|
return {
|
||||||
creditsUsageLimit: operationsData.credits,
|
creditsUsageLimit: operationsData.credits,
|
||||||
userEmail: operationsData.userEmail,
|
userEmail: operationsData.userEmail,
|
||||||
userPassword: operationsData.password?md5(operationsData.password + "abc"):'',
|
userPassword: operationsData.password ? md5(operationsData.password + 'abc') : '',
|
||||||
userName: operationsData.userName,
|
userName: operationsData.userName,
|
||||||
};
|
subscriptionPlanId: operationsData.subscriptionPlanId
|
||||||
};
|
}
|
||||||
|
}
|
||||||
let setEditData = () => {
|
let setEditData = () => {
|
||||||
return {
|
return {
|
||||||
id: operationsData.accountId,
|
id: operationsData.accountId,
|
||||||
@@ -198,57 +245,63 @@ export default defineComponent({
|
|||||||
userPassword:
|
userPassword:
|
||||||
operationsData.password == operationsData.oldPassword
|
operationsData.password == operationsData.oldPassword
|
||||||
? null
|
? null
|
||||||
: md5(operationsData.password + "abc"),
|
: md5(operationsData.password + 'abc'),
|
||||||
};
|
subscriptionPlanId: operationsData.subscriptionPlanId
|
||||||
};
|
|
||||||
let cancelDsign = () => {
|
|
||||||
operationsData.accountId = -1;
|
|
||||||
operationsData.userName = "";
|
|
||||||
operationsData.userEmail = "";
|
|
||||||
operationsData.password = "";
|
|
||||||
operationsData.credits = "";
|
|
||||||
operations.operationsModal = false;
|
|
||||||
};
|
|
||||||
let setOk = () => {
|
|
||||||
let data;
|
|
||||||
if (operations.title?.value == "Add") {
|
|
||||||
data = setAddData();
|
|
||||||
if (!isEmail(data.userEmail)) {
|
|
||||||
message.info(t('admin.jsContent1'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!data.userName ||
|
|
||||||
!data.userEmail ||
|
|
||||||
!data.userPassword
|
|
||||||
)
|
|
||||||
return message.warning(t('admin.jsContent2'));
|
|
||||||
Https.axiosPost(Https.httpUrls.addOrUpdateSubAccount, data).then(
|
|
||||||
(rv) => {
|
|
||||||
if (rv) {
|
|
||||||
cancelDsign();
|
|
||||||
emit("searchHistoryList");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
data = setEditData();
|
|
||||||
if (!isEmail(data.userEmail)) {
|
|
||||||
message.info("The email format is incorrect");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!data.userName || !data.userEmail)
|
|
||||||
return message.warning("Please check the input box marked with *");
|
|
||||||
Https.axiosPost(Https.httpUrls.addOrUpdateSubAccount, data).then(
|
|
||||||
(rv) => {
|
|
||||||
if (rv) {
|
|
||||||
cancelDsign();
|
|
||||||
emit("searchHistoryList");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
let cancelDsign = () => {
|
||||||
|
operationsData.accountId = -1
|
||||||
|
operationsData.userName = ''
|
||||||
|
operationsData.userEmail = ''
|
||||||
|
operationsData.password = ''
|
||||||
|
operationsData.credits = ''
|
||||||
|
operationsData.subscriptionPlanId = ''
|
||||||
|
operationsData.oldSubscriptionPlanId = ''
|
||||||
|
operations.operationsModal = false
|
||||||
|
}
|
||||||
|
let setOk = () => {
|
||||||
|
let data
|
||||||
|
if (operations.title?.value == 'Add') {
|
||||||
|
data = setAddData()
|
||||||
|
if (!isEmail(data.userEmail)) {
|
||||||
|
message.info(t('admin.jsContent1'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!data.userName || !data.userEmail || !data.userPassword)
|
||||||
|
return message.warning(t('admin.jsContent2'))
|
||||||
|
Https.axiosPost(Https.httpUrls.addOrUpdateSubAccount, data).then(rv => {
|
||||||
|
if (rv) {
|
||||||
|
cancelDsign()
|
||||||
|
emit('searchHistoryList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
data = setEditData()
|
||||||
|
if (!isEmail(data.userEmail)) {
|
||||||
|
message.info('The email format is incorrect')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!data.userName || !data.userEmail || !data.subscriptionPlanId)
|
||||||
|
return message.warning('Please check the input box marked with *')
|
||||||
|
const needSwitchPlan =
|
||||||
|
operationsData.subscriptionPlanId &&
|
||||||
|
operationsData.subscriptionPlanId !== operationsData.oldSubscriptionPlanId
|
||||||
|
Https.axiosPost(Https.httpUrls.addOrUpdateSubAccount, data).then(async rv => {
|
||||||
|
if (rv) {
|
||||||
|
if (needSwitchPlan) {
|
||||||
|
await Https.axiosGet(Https.httpUrls.switchSubAccountSubscribePlan, {
|
||||||
|
params: {
|
||||||
|
targetSubscriptionPlanId: operationsData.subscriptionPlanId,
|
||||||
|
subAccId: operationsData.accountId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
cancelDsign()
|
||||||
|
emit('searchHistoryList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...toRefs(operations),
|
...toRefs(operations),
|
||||||
...toRefs(operationsData),
|
...toRefs(operationsData),
|
||||||
@@ -258,14 +311,16 @@ export default defineComponent({
|
|||||||
focus,
|
focus,
|
||||||
blur,
|
blur,
|
||||||
setOk,
|
setOk,
|
||||||
};
|
planOptions,
|
||||||
|
activePlanOptions
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
methods: {},
|
methods: {}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:deep(.allUserPoeration_modal) {
|
:deep(.allUserPoeration_modal) {
|
||||||
|
|||||||
@@ -36,27 +36,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>{{ $t("admin.Email") }}:</span>
|
<span>{{ $t("admin.Email") }}:</span>
|
||||||
<input
|
<SelectUser v-model="email" labelKey="email" valueKey="email" />
|
||||||
v-model="email"
|
|
||||||
:placeholder="$t('admin.enterEmail')"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
type="text"
|
|
||||||
style="width: 250px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="admin_state_item">
|
|
||||||
<span>{{ $t("admin.UserName") }}:</span>
|
|
||||||
<a-select
|
|
||||||
v-model:value="ids"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
:placeholder="$t('admin.selectUserName')"
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="allUserList"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>Organization Name:</span>
|
<span>Organization Name:</span>
|
||||||
<input
|
<input
|
||||||
@@ -100,8 +82,9 @@
|
|||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { Https } from "@/tool/https";
|
import { Https } from "@/tool/https";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import SelectUser from "@/component/common/SelectUser.vue";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: { SelectUser },
|
||||||
setup() {
|
setup() {
|
||||||
const store: any = useStore();
|
const store: any = useStore();
|
||||||
let rangePickerValue: any = ref([]);
|
let rangePickerValue: any = ref([]);
|
||||||
@@ -176,9 +159,6 @@
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let allUserList: any = computed(() => {
|
|
||||||
return store.state.adminPage.allUserList;
|
|
||||||
});
|
|
||||||
let ids = ref([]);
|
let ids = ref([]);
|
||||||
let email = ref("");
|
let email = ref("");
|
||||||
let dataList: any = ref([]);
|
let dataList: any = ref([]);
|
||||||
@@ -193,7 +173,6 @@
|
|||||||
rangeTimeValue,
|
rangeTimeValue,
|
||||||
columns,
|
columns,
|
||||||
dataList,
|
dataList,
|
||||||
allUserList,
|
|
||||||
ids,
|
ids,
|
||||||
email,
|
email,
|
||||||
renameData,
|
renameData,
|
||||||
@@ -251,7 +230,7 @@
|
|||||||
endTime: endDate,
|
endTime: endDate,
|
||||||
startTime: startDate,
|
startTime: startDate,
|
||||||
ids: ids,
|
ids: ids,
|
||||||
email: this.email.trim(),
|
email: this.email?.trim(),
|
||||||
organizationName: this.organizationName,
|
organizationName: this.organizationName,
|
||||||
};
|
};
|
||||||
Https.axiosGet(Https.httpUrls.getDesignStatistic, {
|
Https.axiosGet(Https.httpUrls.getDesignStatistic, {
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export default defineComponent({
|
|||||||
changeEvent:this.changeEvent,
|
changeEvent:this.changeEvent,
|
||||||
size:this.pageSize,
|
size:this.pageSize,
|
||||||
page:this.currentPage,
|
page:this.currentPage,
|
||||||
email:this.email.trim(),
|
email:this.email?.trim(),
|
||||||
}
|
}
|
||||||
Https.axiosPost(Https.httpUrls.getGenerateFrequency,data).then((rv: any) => {
|
Https.axiosPost(Https.httpUrls.getGenerateFrequency,data).then((rv: any) => {
|
||||||
if (rv) {
|
if (rv) {
|
||||||
|
|||||||
@@ -25,15 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>{{ $t('admin.UserName') }}:</span>
|
<span>{{ $t('admin.UserName') }}:</span>
|
||||||
<a-select
|
<SelectUser v-model="userIdList" labelKey="email" multiple />
|
||||||
v-model:value="userIdList"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 280px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
:placeholder="$t('admin.selectUserName')"
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="dataList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -78,16 +70,15 @@ import { LabelLayout } from 'echarts/features';
|
|||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import SelectUser from '@/component/common/SelectUser.vue'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
SelectUser
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
const store:any = useStore()
|
const store:any = useStore()
|
||||||
let filter:any = reactive({
|
let filter:any = reactive({
|
||||||
dataList:computed(()=>{
|
|
||||||
return store.state.adminPage.allUserList
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let filterData:any = reactive({
|
let filterData:any = reactive({
|
||||||
|
|||||||
@@ -32,27 +32,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>Email:</span>
|
<span>Email:</span>
|
||||||
<input
|
<!-- <input
|
||||||
v-model="email"
|
v-model="email"
|
||||||
placeholder="Please enter email"
|
placeholder="Please enter email"
|
||||||
@keydown.enter="gettrialList"
|
@keydown.enter="gettrialList"
|
||||||
type="text"
|
type="text"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
<SelectUser v-model="email" labelKey="email" valueKey="email" />
|
||||||
<div class="admin_state_item">
|
|
||||||
<span>User Name:</span>
|
|
||||||
<a-select
|
|
||||||
v-model:value="ids"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
placeholder="Select Item..."
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="allUserList"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>Organization Name:</span>
|
<span>Organization Name:</span>
|
||||||
<input
|
<input
|
||||||
@@ -95,8 +84,11 @@
|
|||||||
import { defineComponent, ref, createVNode, computed } from "vue";
|
import { defineComponent, ref, createVNode, computed } from "vue";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { Https } from "@/tool/https";
|
import { Https } from "@/tool/https";
|
||||||
|
import SelectUser from "@/component/common/SelectUser.vue";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {
|
||||||
|
SelectUser
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store: any = useStore();
|
const store: any = useStore();
|
||||||
let rangePickerValue: any = ref([]);
|
let rangePickerValue: any = ref([]);
|
||||||
@@ -238,9 +230,6 @@
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let allUserList: any = computed(() => {
|
|
||||||
return store.state.adminPage.allUserList;
|
|
||||||
});
|
|
||||||
let ids = ref([]);
|
let ids = ref([]);
|
||||||
let email = ref("");
|
let email = ref("");
|
||||||
let dataList: any = ref([]);
|
let dataList: any = ref([]);
|
||||||
@@ -255,7 +244,6 @@
|
|||||||
rangeTimeValue,
|
rangeTimeValue,
|
||||||
columns,
|
columns,
|
||||||
dataList,
|
dataList,
|
||||||
allUserList,
|
|
||||||
ids,
|
ids,
|
||||||
email,
|
email,
|
||||||
renameData,
|
renameData,
|
||||||
@@ -312,7 +300,7 @@
|
|||||||
endTime: endDate,
|
endTime: endDate,
|
||||||
startTime: startDate,
|
startTime: startDate,
|
||||||
ids: ids,
|
ids: ids,
|
||||||
email: this.email.trim(),
|
email: this.email?.trim(),
|
||||||
organizationName: this.organizationName,
|
organizationName: this.organizationName,
|
||||||
};
|
};
|
||||||
Https.axiosGet(Https.httpUrls.getDesignStatistic, {
|
Https.axiosGet(Https.httpUrls.getDesignStatistic, {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
placeholder="Select Item..."
|
placeholder="Select Item..."
|
||||||
max-tag-count="responsive"
|
max-tag-count="responsive"
|
||||||
:options="countryList"
|
:options="allCountry"
|
||||||
></a-select>
|
></a-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
@@ -192,9 +192,6 @@ export default defineComponent({
|
|||||||
cityList: computed(()=>{
|
cityList: computed(()=>{
|
||||||
return store.state.adminPage.city
|
return store.state.adminPage.city
|
||||||
}),
|
}),
|
||||||
countryList: computed(()=>{
|
|
||||||
return store.state.adminPage.country
|
|
||||||
}),
|
|
||||||
isAwayOrUnfold:false,
|
isAwayOrUnfold:false,
|
||||||
});
|
});
|
||||||
let filterData: any = reactive({
|
let filterData: any = reactive({
|
||||||
@@ -471,9 +468,10 @@ export default defineComponent({
|
|||||||
filter.dataList = rv.content;
|
filter.dataList = rv.content;
|
||||||
filterData.total = rv.total;
|
filterData.total = rv.total;
|
||||||
filter.tableLoading = false;
|
filter.tableLoading = false;
|
||||||
rv.content.forEach((item: any) => {
|
filterData.totalPayer = rv.content.reduce((total: number, item: any) => {
|
||||||
filterData.totalPayer += Number(item.payerTotal)
|
const value = item && item.status === 'Success' ? parseFloat(item.payerTotal) : 0;
|
||||||
})
|
return total + (isNaN(value) ? 0 : value);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// this.workspaceItem.position = this.singleTypeList[0].label
|
// this.workspaceItem.position = this.singleTypeList[0].label
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,9 +139,6 @@ export default defineComponent({
|
|||||||
let filter: any = reactive({
|
let filter: any = reactive({
|
||||||
dataList: [],
|
dataList: [],
|
||||||
tableLoading: false,
|
tableLoading: false,
|
||||||
allUserList: computed(()=>{
|
|
||||||
return store.state.adminPage.allUserList
|
|
||||||
}),
|
|
||||||
rowSelection:computed(() => {
|
rowSelection:computed(() => {
|
||||||
return {
|
return {
|
||||||
selectedRowKeys: unref(selectedRowKeys),
|
selectedRowKeys: unref(selectedRowKeys),
|
||||||
|
|||||||
@@ -40,27 +40,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>Email:</span>
|
<span>Email:</span>
|
||||||
<input
|
<!-- <input
|
||||||
v-model="email"
|
v-model="email"
|
||||||
placeholder="Please enter email"
|
placeholder="Please enter email"
|
||||||
@keydown.enter="gettrialList"
|
@keydown.enter="gettrialList"
|
||||||
type="text"
|
type="text"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
<SelectUser v-model="email" labelKey="email" valueKey="email" />
|
||||||
<div class="admin_state_item">
|
|
||||||
<span>User Name:</span>
|
|
||||||
<a-select
|
|
||||||
v-model:value="ids"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
placeholder="Select Item..."
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="allUserList"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>User Type:</span>
|
<span>User Type:</span>
|
||||||
<a-select
|
<a-select
|
||||||
@@ -160,16 +149,14 @@ import { formatTime } from "@/tool/util";
|
|||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { Https } from "@/tool/https";
|
import { Https } from "@/tool/https";
|
||||||
import allUserPoerationsVue from "./allUserPoerations.vue";
|
import allUserPoerationsVue from "./allUserPoerations.vue";
|
||||||
|
import SelectUser from '@/component/common/SelectUser.vue'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {allUserPoerationsVue,},
|
components: {allUserPoerationsVue,SelectUser},
|
||||||
setup() {
|
setup() {
|
||||||
const store:any = useStore()
|
const store:any = useStore()
|
||||||
let filter: any = reactive({
|
let filter: any = reactive({
|
||||||
dataList: [],
|
dataList: [],
|
||||||
tableLoading: false,
|
tableLoading: false,
|
||||||
allUserList: computed(()=>{
|
|
||||||
return store.state.adminPage.allUserList
|
|
||||||
}),
|
|
||||||
allCountry:[],
|
allCountry:[],
|
||||||
isAwayOrUnfold:false
|
isAwayOrUnfold:false
|
||||||
});
|
});
|
||||||
@@ -436,7 +423,7 @@ export default defineComponent({
|
|||||||
page: filterData.currentPage,
|
page: filterData.currentPage,
|
||||||
systemUser: filterData.systemUser,
|
systemUser: filterData.systemUser,
|
||||||
country: filterData.country,
|
country: filterData.country,
|
||||||
email: filterData.email.trim(),
|
email: filterData.email?.trim(),
|
||||||
userType: filterData.userType,
|
userType: filterData.userType,
|
||||||
ids: filterData.ids,
|
ids: filterData.ids,
|
||||||
occupation: filterData.occupation,
|
occupation: filterData.occupation,
|
||||||
|
|||||||
@@ -1,352 +1,392 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="allUserPoerationModal" ref="allUserPoerationModal"></div>
|
<div class="allUserPoerationModal" ref="allUserPoerationModal"></div>
|
||||||
<a-modal
|
<a-modal
|
||||||
class="allUserPoeration_modal generalModel"
|
class="allUserPoeration_modal generalModel"
|
||||||
v-model:visible="operationsModal"
|
v-model:visible="operationsModal"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
:get-container="() => $refs.allUserPoerationModal"
|
:get-container="() => $refs.allUserPoerationModal"
|
||||||
width="50%"
|
width="50%"
|
||||||
:maskClosable="false"
|
:maskClosable="false"
|
||||||
:centered="true"
|
:centered="true"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
:mask="true"
|
:mask="true"
|
||||||
wrapClassName="#app"
|
wrapClassName="#app"
|
||||||
:keyboard="false"
|
:keyboard="false"
|
||||||
>
|
>
|
||||||
<div class="generalModel_btn">
|
<div class="generalModel_btn">
|
||||||
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
|
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
<circle cx="23" cy="23" r="23" fill="#000" fill-opacity="0.3"/>
|
width="100%"
|
||||||
<rect x="32.5063" y="12" width="3" height="29" rx="1.5" transform="rotate(45 32.5063 12)" fill="white"/>
|
height="100%"
|
||||||
<rect x="34.6274" y="32.5059" width="3" height="29" rx="1.5" transform="rotate(135 34.6274 32.5059)" fill="white"/>
|
viewBox="0 0 46 46"
|
||||||
</svg>
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</div>
|
>
|
||||||
</div>
|
<circle cx="23" cy="23" r="23" fill="#000" fill-opacity="0.3" />
|
||||||
<div class="modal_title_text">
|
<rect
|
||||||
<div>{{ title }} User</div>
|
x="32.5063"
|
||||||
</div>
|
y="12"
|
||||||
<div class="allUserPoeration_center admin_page">
|
width="3"
|
||||||
<div class="admin_state_item">
|
height="29"
|
||||||
<span>User Name: <span>*</span></span>
|
rx="1.5"
|
||||||
<input
|
transform="rotate(45 32.5063 12)"
|
||||||
:disabled="title != 'Add'"
|
fill="white"
|
||||||
:class="{active:title != 'Add'}"
|
/>
|
||||||
v-model="userName"
|
<rect
|
||||||
placeholder="Please enter user name"
|
x="34.6274"
|
||||||
type="text"
|
y="32.5059"
|
||||||
style="width: 250px"
|
width="3"
|
||||||
/>
|
height="29"
|
||||||
</div>
|
rx="1.5"
|
||||||
<div class="admin_state_item">
|
transform="rotate(135 34.6274 32.5059)"
|
||||||
<span>User Email: <span>*</span></span>
|
fill="white"
|
||||||
<input
|
/>
|
||||||
:disabled="title != 'Add'"
|
</svg>
|
||||||
:class="{active:title != 'Add'}"
|
</div>
|
||||||
v-model="userEmail"
|
</div>
|
||||||
placeholder="Please enter email"
|
<div class="modal_title_text">
|
||||||
type="text"
|
<div>{{ title }} User</div>
|
||||||
style="width: 250px"
|
</div>
|
||||||
/>
|
<div class="allUserPoeration_center admin_page">
|
||||||
</div>
|
<div class="admin_state_item">
|
||||||
<div class="admin_state_item">
|
<span>
|
||||||
<span>Create Time: <span>*</span></span>
|
User Name:
|
||||||
<a-date-picker :disabled="title != 'Add'" style="width: 250px" valueFormat="YYYY-MM-DDTHH:mm:ss" class="range_picker" show-time placeholder="Create Time" v-model:value="validStartTime">
|
<span>*</span>
|
||||||
<template #suffixIcon>
|
</span>
|
||||||
<span
|
<input
|
||||||
class="icon iconfont range_picker_icon icon-rili"
|
:disabled="title != 'Add'"
|
||||||
></span>
|
:class="{ active: title != 'Add' }"
|
||||||
</template>
|
v-model="userName"
|
||||||
</a-date-picker>
|
placeholder="Please enter user name"
|
||||||
</div>
|
type="text"
|
||||||
<div class="admin_state_item">
|
style="width: 250px"
|
||||||
<span>End Time: <span>*</span></span>
|
/>
|
||||||
<a-date-picker style="width: 250px" valueFormat="YYYY-MM-DDTHH:mm:ss" class="range_picker" show-time placeholder="End Time" v-model:value="validEndTime">
|
</div>
|
||||||
<template #suffixIcon>
|
<div class="admin_state_item">
|
||||||
<span
|
<span>
|
||||||
class="icon iconfont range_picker_icon icon-rili"
|
User Email:
|
||||||
></span>
|
<span>*</span>
|
||||||
</template>
|
</span>
|
||||||
</a-date-picker>
|
<input
|
||||||
</div>
|
:disabled="title != 'Add'"
|
||||||
<div class="admin_state_item">
|
:class="{ active: title != 'Add' }"
|
||||||
<span>User Type:<span>*</span></span>
|
v-model="userEmail"
|
||||||
<a-select
|
placeholder="Please enter email"
|
||||||
v-model:value="systemUser"
|
type="text"
|
||||||
size="large"
|
style="width: 250px"
|
||||||
style="width: 250px"
|
/>
|
||||||
optionFilterProp="label"
|
</div>
|
||||||
:options="state"
|
<div class="admin_state_item">
|
||||||
placeholder="Please select"
|
<span>
|
||||||
allowClear
|
Create Time:
|
||||||
show-search
|
<span>*</span>
|
||||||
></a-select>
|
</span>
|
||||||
</div>
|
<a-date-picker
|
||||||
<div class="admin_state_item">
|
:disabled="title != 'Add'"
|
||||||
<span>Credits:</span>
|
style="width: 250px"
|
||||||
<input
|
valueFormat="YYYY-MM-DDTHH:mm:ss"
|
||||||
v-model="credits"
|
class="range_picker"
|
||||||
placeholder="Please enter credits"
|
show-time
|
||||||
type="text"
|
placeholder="Create Time"
|
||||||
style="width: 250px"
|
v-model:value="validStartTime"
|
||||||
/>
|
>
|
||||||
</div>
|
<template #suffixIcon>
|
||||||
<div class="admin_state_item">
|
<span class="icon iconfont range_picker_icon icon-rili"></span>
|
||||||
<span>Country or Region:</span>
|
</template>
|
||||||
<input
|
</a-date-picker>
|
||||||
|
</div>
|
||||||
|
<div class="admin_state_item">
|
||||||
|
<span>
|
||||||
|
End Time:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
|
<a-date-picker
|
||||||
|
style="width: 250px"
|
||||||
|
valueFormat="YYYY-MM-DDTHH:mm:ss"
|
||||||
|
class="range_picker"
|
||||||
|
show-time
|
||||||
|
placeholder="End Time"
|
||||||
|
v-model:value="validEndTime"
|
||||||
|
>
|
||||||
|
<template #suffixIcon>
|
||||||
|
<span class="icon iconfont range_picker_icon icon-rili"></span>
|
||||||
|
</template>
|
||||||
|
</a-date-picker>
|
||||||
|
</div>
|
||||||
|
<div class="admin_state_item">
|
||||||
|
<span>
|
||||||
|
User Type:
|
||||||
|
<span>*</span>
|
||||||
|
</span>
|
||||||
|
<a-select
|
||||||
|
v-model:value="systemUser"
|
||||||
|
size="large"
|
||||||
|
style="width: 250px"
|
||||||
|
optionFilterProp="label"
|
||||||
|
:options="state"
|
||||||
|
placeholder="Please select"
|
||||||
|
allowClear
|
||||||
|
show-search
|
||||||
|
></a-select>
|
||||||
|
</div>
|
||||||
|
<div class="admin_state_item">
|
||||||
|
<span>Credits:</span>
|
||||||
|
<input
|
||||||
|
v-model="credits"
|
||||||
|
placeholder="Please enter credits"
|
||||||
|
type="text"
|
||||||
|
style="width: 250px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="admin_state_item">
|
||||||
|
<span>Country or Region:</span>
|
||||||
|
<!-- <input
|
||||||
:disabled="title != 'Add'"
|
:disabled="title != 'Add'"
|
||||||
:class="{active:title != 'Add'}"
|
:class="{active:title != 'Add'}"
|
||||||
v-model="country"
|
v-model="country"
|
||||||
placeholder="Please enter country"
|
placeholder="Please enter country"
|
||||||
type="text"
|
type="text"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
<a-select
|
||||||
<div class="admin_state_item">
|
v-model:value="country"
|
||||||
<span>Organization Name:</span>
|
:disabled="title != 'Add'"
|
||||||
<input
|
:class="{ active: title != 'Add' }"
|
||||||
:disabled="title != 'Add'"
|
:allowClear="true"
|
||||||
:class="{active:title != 'Add'}"
|
show-search
|
||||||
v-model="organizationName"
|
style="width: 250px"
|
||||||
placeholder="Please enter Organization Name"
|
:filter-option="filterOption"
|
||||||
type="text"
|
placeholder="Select Country or Region"
|
||||||
style="width: 250px"
|
max-tag-count="responsive"
|
||||||
/>
|
:options="allCountry"
|
||||||
</div>
|
/>
|
||||||
<div class="admin_state_item">
|
</div>
|
||||||
<span>Sub Account Num:</span>
|
</div>
|
||||||
<input
|
<div class="allUserPoeration_btn admin_page">
|
||||||
:disabled="title != 'Add'"
|
<div class="admin_search_item" @click="cancelDsign">Close</div>
|
||||||
:class="{active:title != 'Add'}"
|
<div class="admin_search_item" @click="setOk">OK</div>
|
||||||
v-model="subAccountNum"
|
</div>
|
||||||
placeholder="Please enter Sub Account Num"
|
</a-modal>
|
||||||
type="number"
|
<div class="mark_loading" v-show="loadingShow">
|
||||||
style="width: 250px"
|
<a-spin size="large" />
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="allUserPoeration_btn admin_page">
|
|
||||||
<div class="admin_search_item" @click="cancelDsign">
|
|
||||||
Close
|
|
||||||
</div>
|
|
||||||
<div class="admin_search_item" @click="setOk">
|
|
||||||
OK
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
<div class="mark_loading" v-show="loadingShow">
|
|
||||||
<a-spin size="large" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, ref, reactive, watch, onMounted, nextTick, toRefs } from "vue";
|
import { defineComponent, ref, reactive, watch, onMounted, nextTick, toRefs } from 'vue'
|
||||||
import { Https } from "@/tool/https";
|
import { Https } from '@/tool/https'
|
||||||
import { Modal, message } from "ant-design-vue";
|
import { Modal, message } from 'ant-design-vue'
|
||||||
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||||
import { formatTime } from "@/tool/util";
|
import { formatTime } from '@/tool/util'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {},
|
||||||
},
|
emits: ['searchHistoryList'],
|
||||||
emits: ['searchHistoryList'],
|
setup(props, { emit }) {
|
||||||
setup(props,{emit}) {
|
let operations = reactive({
|
||||||
let operations = reactive({
|
operationsModal: false,
|
||||||
operationsModal:false,
|
operationsEdit: false,
|
||||||
operationsEdit:false,
|
loadingShow: false,
|
||||||
loadingShow:false,
|
title: ''
|
||||||
title:''
|
})
|
||||||
})
|
let operationsData = reactive({
|
||||||
let operationsData = reactive({
|
accountId: -1,
|
||||||
accountId:-1,
|
userName: '',
|
||||||
userName:'',
|
userEmail: '',
|
||||||
userEmail:'',
|
validStartTime: '',
|
||||||
validStartTime:'',
|
validEndTime: '',
|
||||||
validEndTime:'',
|
systemUser: '',
|
||||||
systemUser:'',
|
credits: '',
|
||||||
credits:'',
|
country: ''
|
||||||
country:'',
|
})
|
||||||
organizationName:'',
|
let state = ref([
|
||||||
subAccountNum:0,
|
{
|
||||||
})
|
label: 'visitor',
|
||||||
let state = ref([
|
value: '0'
|
||||||
{
|
},
|
||||||
label:'visitor',
|
{
|
||||||
value:'0',
|
label: 'yearly',
|
||||||
},
|
value: '1'
|
||||||
{
|
},
|
||||||
label:'yearly',
|
{
|
||||||
value:'1',
|
label: 'monthly',
|
||||||
},
|
value: '2'
|
||||||
{
|
},
|
||||||
label:'monthly',
|
{
|
||||||
value:'2',
|
label: 'trial',
|
||||||
},
|
value: '3'
|
||||||
{
|
},
|
||||||
label:'trial',
|
{
|
||||||
value:'3',
|
label: 'userInEvent',
|
||||||
},
|
value: '4'
|
||||||
{
|
},
|
||||||
label: "userInEvent",
|
{
|
||||||
value: "4",
|
label: 'Edu Admin',
|
||||||
},
|
value: '7'
|
||||||
{
|
}
|
||||||
label: "Edu Admin",
|
])
|
||||||
value: "7",
|
let init = (funStr, data) => {
|
||||||
},
|
operations.operationsModal = true
|
||||||
]);
|
operations.operationsEdit = true
|
||||||
let init = (funStr,data)=>{
|
operations.title = funStr
|
||||||
operations.operationsModal = true
|
if (funStr == 'Add') operations.operationsEdit = false
|
||||||
operations.operationsEdit = true
|
if (funStr == 'Edit') {
|
||||||
operations.title = funStr
|
operationsData.organizationName = data.organizationName
|
||||||
if(funStr == 'Add') operations.operationsEdit = false
|
operationsData.subAccountNum = data.subAccountNum ? data.subAccountNum : 0
|
||||||
if(funStr == 'Edit'){
|
let startTime = data.validStartTime
|
||||||
operationsData.organizationName = data.organizationName
|
? formatTime(data.validStartTime / 1000, 'YYYY-MM-DDThh:mm:ss')
|
||||||
operationsData.subAccountNum = data.subAccountNum?data.subAccountNum:0
|
: ''
|
||||||
let startTime = data.validStartTime?formatTime(data.validStartTime / 1000,"YYYY-MM-DDThh:mm:ss"):''
|
let endTime = data.validEndTime
|
||||||
let endTime = data.validEndTime?formatTime(data.validEndTime / 1000,"YYYY-MM-DDThh:mm:ss"):''
|
? formatTime(data.validEndTime / 1000, 'YYYY-MM-DDThh:mm:ss')
|
||||||
operationsData.accountId=data.id
|
: ''
|
||||||
operationsData.userName=data.userName
|
operationsData.accountId = data.id
|
||||||
operationsData.userEmail=data.userEmail
|
operationsData.userName = data.userName
|
||||||
// operationsData.validStartTime='2024-08-05T00:00:06'
|
operationsData.userEmail = data.userEmail
|
||||||
// operationsData.validEndTime='2024-08-05T00:00:06'
|
// operationsData.validStartTime='2024-08-05T00:00:06'
|
||||||
operationsData.validStartTime=startTime
|
// operationsData.validEndTime='2024-08-05T00:00:06'
|
||||||
operationsData.validEndTime=endTime
|
operationsData.validStartTime = startTime
|
||||||
operationsData.systemUser=String(data.systemUser)
|
operationsData.validEndTime = endTime
|
||||||
operationsData.credits=data.credits
|
operationsData.systemUser = String(data.systemUser)
|
||||||
operationsData.country=data.country
|
operationsData.credits = data.credits
|
||||||
// operationsData.accountId = data.accountId
|
operationsData.country = data.country
|
||||||
// operationsData.userName = data.userName
|
// operationsData.accountId = data.accountId
|
||||||
// operationsData.userEmail = data.userEmail
|
// operationsData.userName = data.userName
|
||||||
// operationsData.validStartTime = formatTime(data.validStartTime)
|
// operationsData.userEmail = data.userEmail
|
||||||
// operationsData.validEndTime = formatTime(data.validEndTime)
|
// operationsData.validStartTime = formatTime(data.validStartTime)
|
||||||
}
|
// operationsData.validEndTime = formatTime(data.validEndTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let setTime = time => {
|
||||||
|
if (time) {
|
||||||
|
const date = new Date(time)
|
||||||
|
const timestamp = date.getTime() // 转换为秒数
|
||||||
|
return timestamp
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let setAddData = () => {
|
||||||
|
return {
|
||||||
|
country: operationsData.country,
|
||||||
|
credits: operationsData.credits,
|
||||||
|
systemUser: operationsData.systemUser,
|
||||||
|
userEmail: operationsData.userEmail,
|
||||||
|
userName: operationsData.userName,
|
||||||
|
validEndTime: setTime(operationsData.validEndTime),
|
||||||
|
validStartTime: setTime(operationsData.validStartTime),
|
||||||
|
organizationName: operationsData.organizationName
|
||||||
|
? operationsData.organizationName
|
||||||
|
: null,
|
||||||
|
subAccountNum: operationsData.subAccountNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let setEditData = () => {
|
||||||
|
return {
|
||||||
|
accountId: operationsData.accountId,
|
||||||
|
credits: operationsData.credits,
|
||||||
|
systemUser: operationsData.systemUser,
|
||||||
|
validEndTime: setTime(operationsData.validEndTime),
|
||||||
|
userName: operationsData.userName,
|
||||||
|
userEmail: operationsData.userEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cancelDsign = () => {
|
||||||
|
operationsData.accountId = -1
|
||||||
|
operationsData.userName = ''
|
||||||
|
operationsData.userEmail = ''
|
||||||
|
operationsData.validStartTime = ''
|
||||||
|
operationsData.validEndTime = ''
|
||||||
|
operationsData.systemUser = ''
|
||||||
|
operationsData.credits = ''
|
||||||
|
operationsData.country = ''
|
||||||
|
operations.operationsModal = false
|
||||||
|
}
|
||||||
|
let setOk = () => {
|
||||||
|
let data
|
||||||
|
if (operations.title == 'Add') {
|
||||||
|
data = setAddData()
|
||||||
|
if (
|
||||||
|
!data.userName ||
|
||||||
|
!data.userEmail ||
|
||||||
|
!data.validStartTime ||
|
||||||
|
!data.validEndTime ||
|
||||||
|
!data.systemUser
|
||||||
|
)
|
||||||
|
return message.warning('Please check the input box marked with *')
|
||||||
|
Https.axiosPost(Https.httpUrls.adminAddUser, data).then(rv => {
|
||||||
|
if (rv) {
|
||||||
|
cancelDsign()
|
||||||
|
emit('searchHistoryList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
data = setEditData()
|
||||||
|
if (!data.userName || !data.userEmail || !data.validEndTime || !data.systemUser)
|
||||||
|
return message.warning('Please check the input box marked with *')
|
||||||
|
Https.axiosPost(Https.httpUrls.modifyUser, {}, { params: data }).then(rv => {
|
||||||
|
if (rv) {
|
||||||
|
cancelDsign()
|
||||||
|
emit('searchHistoryList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
const allCountry = ref([])
|
||||||
let setTime = (time) =>{
|
const filterOption = (input, option) => {
|
||||||
if(time){
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
const date = new Date(time);
|
}
|
||||||
const timestamp = date.getTime(); // 转换为秒数
|
onMounted(() => {
|
||||||
return timestamp
|
const countryList = sessionStorage.getItem('allCountry')
|
||||||
}else{
|
if (countryList) {
|
||||||
return ''
|
allCountry.value = JSON.parse(countryList)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
return {
|
||||||
let setAddData = ()=>{
|
...toRefs(operations),
|
||||||
return {
|
...toRefs(operationsData),
|
||||||
"country": operationsData.country,
|
state,
|
||||||
"credits": operationsData.credits,
|
cancelDsign,
|
||||||
"systemUser": operationsData.systemUser,
|
init,
|
||||||
"userEmail": operationsData.userEmail,
|
setOk,
|
||||||
"userName": operationsData.userName,
|
allCountry,
|
||||||
"validEndTime": setTime(operationsData.validEndTime),
|
filterOption
|
||||||
"validStartTime": setTime(operationsData.validStartTime),
|
}
|
||||||
"organizationName": operationsData.organizationName?operationsData.organizationName:null,
|
}
|
||||||
"subAccountNum": operationsData.subAccountNum,
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
let setEditData = ()=>{
|
|
||||||
return {
|
|
||||||
"accountId": operationsData.accountId,
|
|
||||||
"credits": operationsData.credits,
|
|
||||||
"systemUser": operationsData.systemUser,
|
|
||||||
"validEndTime": setTime(operationsData.validEndTime),
|
|
||||||
"userName": operationsData.userName,
|
|
||||||
"userEmail": operationsData.userEmail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let cancelDsign = ()=>{
|
|
||||||
operationsData.accountId=-1
|
|
||||||
operationsData.userName=''
|
|
||||||
operationsData.userEmail=''
|
|
||||||
operationsData.validStartTime=''
|
|
||||||
operationsData.validEndTime=''
|
|
||||||
operationsData.systemUser=''
|
|
||||||
operationsData.credits=''
|
|
||||||
operationsData.country=''
|
|
||||||
operations.operationsModal = false
|
|
||||||
}
|
|
||||||
let setOk = ()=>{
|
|
||||||
let data
|
|
||||||
if(operations.title == 'Add'){
|
|
||||||
data = setAddData()
|
|
||||||
if(!data.userName || !data.userEmail || !data.validStartTime || !data.validEndTime || !data.systemUser)return message.warning('Please check the input box marked with *')
|
|
||||||
Https.axiosPost(Https.httpUrls.adminAddUser, data).then(
|
|
||||||
(rv) => {
|
|
||||||
if (rv) {
|
|
||||||
cancelDsign()
|
|
||||||
emit('searchHistoryList')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}else{
|
|
||||||
data = setEditData()
|
|
||||||
if(!data.userName || !data.userEmail || !data.validEndTime || !data.systemUser)return message.warning('Please check the input box marked with *')
|
|
||||||
Https.axiosPost(Https.httpUrls.modifyUser,{},{params:data}).then(
|
|
||||||
(rv) => {
|
|
||||||
if (rv) {
|
|
||||||
cancelDsign()
|
|
||||||
emit('searchHistoryList')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...toRefs(operations),
|
|
||||||
...toRefs(operationsData),
|
|
||||||
state,
|
|
||||||
cancelDsign,
|
|
||||||
init,
|
|
||||||
setOk,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:deep(.allUserPoeration_modal){
|
:deep(.allUserPoeration_modal) {
|
||||||
.ant-modal-body{
|
.ant-modal-body {
|
||||||
height: auto;
|
height: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
.allUserPoeration_modal {
|
.allUserPoeration_modal {
|
||||||
.closeIcon {
|
.closeIcon {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
}
|
||||||
|
> .admin_state_item {
|
||||||
|
> span {
|
||||||
|
width: 15rem;
|
||||||
}
|
}
|
||||||
> .admin_state_item{
|
}
|
||||||
> span{
|
.allUserPoeration_btn {
|
||||||
width: 15rem;
|
display: flex;
|
||||||
}
|
flex-direction: row;
|
||||||
}
|
height: auto;
|
||||||
.allUserPoeration_btn{
|
justify-content: flex-end;
|
||||||
display: flex;
|
padding: 1rem 0;
|
||||||
flex-direction: row;
|
.admin_search_item {
|
||||||
height: auto;
|
margin-bottom: 0;
|
||||||
justify-content: flex-end;
|
}
|
||||||
padding: 1rem 0;
|
}
|
||||||
.admin_search_item{
|
.allUserPoeration_center {
|
||||||
margin-bottom: 0;
|
flex: 1;
|
||||||
}
|
overflow-y: auto;
|
||||||
}
|
flex-direction: row;
|
||||||
.allUserPoeration_center{
|
flex-wrap: wrap;
|
||||||
flex: 1;
|
}
|
||||||
overflow-y: auto;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,15 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>User:</span>
|
<span>User:</span>
|
||||||
<a-select
|
<SelectUser v-model="userIdList" labelKey="email" multiple />
|
||||||
v-model:value="userIdList"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 280px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
placeholder="Select Item..."
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="dataList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -62,15 +54,14 @@ import { PieChart } from 'echarts/charts';
|
|||||||
import { LabelLayout } from 'echarts/features';
|
import { LabelLayout } from 'echarts/features';
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import SelectUser from '@/component/common/SelectUser.vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
SelectUser
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store:any = useStore()
|
const store:any = useStore()
|
||||||
let filter:any = reactive({
|
let filter:any = reactive({
|
||||||
dataList:computed(()=>{
|
|
||||||
return store.state.adminPage.allUserList
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let filterData:any = reactive({
|
let filterData:any = reactive({
|
||||||
|
|||||||
1133
src/component/Administrator/subscriptionPlan.vue
Normal file
@@ -37,26 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin_state_item">
|
<div class="admin_state_item">
|
||||||
<span>Email:</span>
|
<span>Email:</span>
|
||||||
<input
|
<SelectUser v-model="email" labelKey="email" valueKey="email" />
|
||||||
v-model="email"
|
|
||||||
placeholder="Please enter email"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
type="text"
|
|
||||||
style="width: 250px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="admin_state_item">
|
|
||||||
<span>User Name:</span>
|
|
||||||
<a-select
|
|
||||||
v-model:value="ids"
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
placeholder="Select Item..."
|
|
||||||
max-tag-count="responsive"
|
|
||||||
:options="allUserList"
|
|
||||||
@keydown.enter="gettrialList"
|
|
||||||
></a-select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin_search">
|
<div class="admin_search">
|
||||||
@@ -96,17 +77,17 @@ import { defineComponent, ref, createVNode, computed, reactive, toRefs, onMounte
|
|||||||
import { formatTime } from "@/tool/util";
|
import { formatTime } from "@/tool/util";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { Https } from "@/tool/https";
|
import { Https } from "@/tool/https";
|
||||||
|
import SelectUser from '@/component/common/SelectUser.vue'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
SelectUser
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store:any = useStore()
|
const store:any = useStore()
|
||||||
let filter:any = reactive({
|
let filter:any = reactive({
|
||||||
dataList:[],
|
dataList:[],
|
||||||
tableLoading:false,
|
tableLoading:false,
|
||||||
allUserList: computed(()=>{
|
|
||||||
return store.state.adminPage.allUserList
|
|
||||||
}),
|
|
||||||
allCountry:[]
|
allCountry:[]
|
||||||
})
|
})
|
||||||
let filterData:any = reactive({
|
let filterData:any = reactive({
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
|
|||||||
layer.clippingMask,
|
layer.clippingMask,
|
||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({ absolutePositioned: true });
|
clippingMaskFabricObject.set({ absolutePositioned: true });
|
||||||
this.newFill = new fabric.Rect({
|
this.newFill = new fabric.Rect({
|
||||||
width: clippingMaskFabricObject.width,
|
width: clippingMaskFabricObject.width,
|
||||||
@@ -117,7 +117,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
|
|||||||
this.parent.clippingMask,
|
this.parent.clippingMask,
|
||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({ absolutePositioned: true });
|
clippingMaskFabricObject.set({ absolutePositioned: true });
|
||||||
this.newFill = new fabric.Rect({
|
this.newFill = new fabric.Rect({
|
||||||
width: clippingMaskFabricObject.width,
|
width: clippingMaskFabricObject.width,
|
||||||
@@ -222,7 +222,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
|
|||||||
this.parent?.clippingMask,
|
this.parent?.clippingMask,
|
||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
clipPath.clipPath = null;
|
// clipPath.clipPath = null;
|
||||||
clipPath.set({ absolutePositioned: true });
|
clipPath.set({ absolutePositioned: true });
|
||||||
this.group.clipPath = clipPath;
|
this.group.clipPath = clipPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class FillLayerBackgroundCommand extends Command {
|
|||||||
layer.clippingMask,
|
layer.clippingMask,
|
||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
|
|
||||||
clippingMaskFabricObject.set({
|
clippingMaskFabricObject.set({
|
||||||
// 设置绝对定位
|
// 设置绝对定位
|
||||||
|
|||||||
328
src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
import { Command } from "./Command";
|
||||||
|
import { findLayerRecursively } from "../utils/layerHelper";
|
||||||
|
import { fabric } from "fabric-with-all";
|
||||||
|
import {
|
||||||
|
findObjectById,
|
||||||
|
generateId,
|
||||||
|
insertObjectAtZIndex,
|
||||||
|
removeCanvasObjectByObject,
|
||||||
|
createPatternTransform,
|
||||||
|
imageAddGapToCanvas,
|
||||||
|
} from "../utils/helper";
|
||||||
|
import { restoreFabricObject } from "../utils/objectHelper";
|
||||||
|
|
||||||
|
const scale = 0.3;// 默认缩放比例
|
||||||
|
|
||||||
|
export const FillSourceToBase64 = (source) => {
|
||||||
|
if (source?.toDataURL) {
|
||||||
|
return source.toDataURL?.();
|
||||||
|
} else if (source?.src) {
|
||||||
|
return source.src;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充图案平铺命令
|
||||||
|
* 填充重复属性:repeat | repeat-x | repeat-y | no-repeat
|
||||||
|
* 默认缩放比例:0.3
|
||||||
|
* 默认偏移量:50%
|
||||||
|
*/
|
||||||
|
export class FillRepeatCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({ name: "填充图案平铺", saveState: true });
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.layers = options.layers;
|
||||||
|
this.canvasManager = options.canvasManager;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.layerId = options.layerId;
|
||||||
|
this.fillRepeat = options.fillRepeat;
|
||||||
|
this.oldObjects = null;
|
||||||
|
this.oldLocked = null;
|
||||||
|
this.oldIsDisableUnlock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||||
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||||
|
console.warn("图层不存在或没有 fabric 对象");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
||||||
|
if (!object || (object.type !== "rect" && object.type !== "image")) {
|
||||||
|
console.warn("当前对象不能平铺", object.type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.oldObjects = object;
|
||||||
|
if (this.fillRepeat === "no-repeat") {
|
||||||
|
const fill_ = object.fill_;
|
||||||
|
const image = await new Promise((resolve, reject) => {
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
fill_.source,
|
||||||
|
v => resolve(v),
|
||||||
|
{ crossOrigin: "anonymous" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
image.set({
|
||||||
|
id: object.id,
|
||||||
|
layerId: object.layerId,
|
||||||
|
layerName: object.layerName,
|
||||||
|
...(fill_.originalInfo || {
|
||||||
|
top: object.top,
|
||||||
|
left: object.left,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
layer.fabricObjects = [image.toObject(["id", "layerId", "layerName"])];
|
||||||
|
this.oldLocked = layer.locked;
|
||||||
|
layer.locked = false;
|
||||||
|
|
||||||
|
this.canvas.add(image);
|
||||||
|
this.canvas.remove(object);
|
||||||
|
} else {
|
||||||
|
const img = await new Promise((resolve, reject) => {
|
||||||
|
if (object.type === "rect") {
|
||||||
|
let source = object.fill.source;
|
||||||
|
resolve(source);
|
||||||
|
} else if (object.type === "image") {
|
||||||
|
const imgElement = object.getElement();
|
||||||
|
// 创建透明 Canvas
|
||||||
|
const tcanvas = document.createElement('canvas');
|
||||||
|
tcanvas.width = imgElement.width;
|
||||||
|
tcanvas.height = imgElement.height;
|
||||||
|
const ctx = tcanvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
||||||
|
ctx.drawImage(imgElement, 0, 0);
|
||||||
|
resolve(tcanvas);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const fill_ = object.fill_ || {
|
||||||
|
source: FillSourceToBase64(img),
|
||||||
|
gapX: 0,
|
||||||
|
gapY: 0,
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
originalInfo: {
|
||||||
|
top: object.top,
|
||||||
|
left: object.left,
|
||||||
|
scaleX: object.scaleX,
|
||||||
|
scaleY: object.scaleY,
|
||||||
|
width: object.width,
|
||||||
|
height: object.height,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const bgObject = this.canvasManager.getBackgroundLayerObject();
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
source: img,
|
||||||
|
repeat: this.fillRepeat,
|
||||||
|
patternTransform: object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(scale, 0),
|
||||||
|
offsetX: object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : bgObject.width / 2, // 水平偏移
|
||||||
|
offsetY: object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : bgObject.height / 2, // 垂直偏移
|
||||||
|
});
|
||||||
|
const rect = new fabric.Rect({
|
||||||
|
id: object.id,
|
||||||
|
layerId: object.layerId,
|
||||||
|
layerName: object.layerName,
|
||||||
|
fill_,
|
||||||
|
});
|
||||||
|
layer.fabricObjects = [rect.toObject(["id", "layerId", "layerName"])];
|
||||||
|
this.oldLocked = layer.locked;
|
||||||
|
// this.oldIsDisableUnlock = layer.isDisableUnlock;
|
||||||
|
// layer.isDisableUnlock = true;
|
||||||
|
if (this.oldObjects.type === "rect") {
|
||||||
|
rect.set({
|
||||||
|
width: object.width,
|
||||||
|
height: object.height,
|
||||||
|
top: object.top,
|
||||||
|
left: object.left,
|
||||||
|
originX: object.originX,
|
||||||
|
originY: object.originY,
|
||||||
|
angle: object.angle,
|
||||||
|
scaleX: object.scaleX,
|
||||||
|
scaleY: object.scaleY,
|
||||||
|
flipX: object.flipX,
|
||||||
|
flipY: object.flipY,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let scaleX = bgObject.scaleX || 1;
|
||||||
|
let scaleY = bgObject.scaleY || 1;
|
||||||
|
rect.set({
|
||||||
|
width: bgObject.width,
|
||||||
|
height: bgObject.height,
|
||||||
|
top: bgObject.top - bgObject.height * scaleY / 2,
|
||||||
|
left: bgObject.left - bgObject.width * scaleX / 2,
|
||||||
|
scaleX,
|
||||||
|
scaleY,
|
||||||
|
});
|
||||||
|
layer.locked = true;
|
||||||
|
}
|
||||||
|
rect.set("fill", pattern);
|
||||||
|
this.canvas.add(rect);
|
||||||
|
this.canvas.remove(object);
|
||||||
|
}
|
||||||
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||||
|
await this.layerManager?.sortLayersWithTool?.();
|
||||||
|
await this.canvasManager.thumbnailManager?.generateLayerThumbnail(
|
||||||
|
this.layerId
|
||||||
|
);
|
||||||
|
await this.layerManager.selectLayerObjects(this.layerId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async undo() {
|
||||||
|
if (!this.oldObjects) {
|
||||||
|
console.warn("没有旧对象可恢复");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.oldObjects.layerId);
|
||||||
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||||
|
console.warn("图层不存在或没有 fabric 对象");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
||||||
|
this.canvas.remove(object);
|
||||||
|
this.canvas.add(this.oldObjects);
|
||||||
|
layer.fabricObjects = [this.oldObjects.toObject(["id", "layerId", "layerName"])];
|
||||||
|
layer.locked = this.oldLocked;
|
||||||
|
// layer.isDisableUnlock = this.oldIsDisableUnlock;
|
||||||
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||||
|
await this.layerManager?.sortLayersWithTool?.();
|
||||||
|
this.canvas.renderAll();
|
||||||
|
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layerId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充图案更改参数
|
||||||
|
*/
|
||||||
|
export class FillRepeatChangeCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({ name: "填充图案更改参数", saveState: true });
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.layers = options.layers;
|
||||||
|
this.canvasManager = options.canvasManager;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.layerId = options.layerId;
|
||||||
|
this.newPattern = options.newPattern;
|
||||||
|
this.oldPattern = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||||
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||||
|
console.warn("图层不存在或没有 fabric 对象");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
||||||
|
if (!object || object.type !== "rect") {
|
||||||
|
console.warn("当前对象不是矩形", object);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.oldPattern = object.oldPattern || object.get("fill");
|
||||||
|
delete object.oldPattern;
|
||||||
|
const oldPattern = { ...this.oldPattern };
|
||||||
|
delete oldPattern.id;
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
...oldPattern,
|
||||||
|
...this.newPattern,
|
||||||
|
});
|
||||||
|
object.set("fill", pattern);
|
||||||
|
this.canvas.renderAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async undo() {
|
||||||
|
if (!this.oldPattern) {
|
||||||
|
console.warn("没有旧图案可恢复");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||||
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||||
|
console.warn("图层不存在或没有 fabric 对象");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
||||||
|
if (!object || object.type !== "rect") {
|
||||||
|
console.warn("当前对象不是矩形", object);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
...this.oldPattern
|
||||||
|
});
|
||||||
|
object.set("fill", pattern);
|
||||||
|
this.canvas.renderAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充图案更改间隙
|
||||||
|
*/
|
||||||
|
export class FillRepeatGapChangeCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({ name: "填充图案更改间隙", saveState: true });
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.layers = options.layers;
|
||||||
|
this.canvasManager = options.canvasManager;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.layerId = options.layerId;
|
||||||
|
this.newGapX = options.newGapX;
|
||||||
|
this.newGapY = options.newGapY;
|
||||||
|
this.record = !!options.record;
|
||||||
|
this.oldGapX = null;
|
||||||
|
this.oldGapY = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(isUndo = false) {
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||||
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||||
|
console.warn("图层不存在或没有 fabric 对象");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
||||||
|
if (!object || object.type !== "rect") {
|
||||||
|
console.warn("当前对象不是矩形", object);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!object.fill_) {
|
||||||
|
object.fill_ = {
|
||||||
|
source: FillSourceToBase64(object.fill.source),
|
||||||
|
gapX: 0,
|
||||||
|
gapY: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isUndo) {
|
||||||
|
object.fill_.gapX = this.oldGapX;
|
||||||
|
object.fill_.gapY = this.oldGapY;
|
||||||
|
} else {
|
||||||
|
if (!object.oldFill_ && this.record) {
|
||||||
|
object.oldFill_ = { ...object.fill_ };
|
||||||
|
}
|
||||||
|
this.oldGapX = object.fill_.gapX;
|
||||||
|
this.oldGapY = object.fill_.gapY;
|
||||||
|
object.fill_.gapX = this.newGapX;
|
||||||
|
object.fill_.gapY = this.newGapY;
|
||||||
|
}
|
||||||
|
const image = new Image();
|
||||||
|
image.src = object.fill_.source;
|
||||||
|
await image.decode();
|
||||||
|
object.fill_.width = image.width;
|
||||||
|
object.fill_.height = image.height;
|
||||||
|
const fill = object.get("fill");
|
||||||
|
fill.source = imageAddGapToCanvas(image, object.fill_.gapX, object.fill_.gapY);
|
||||||
|
object.set("fill", new fabric.Pattern(fill));
|
||||||
|
this.canvas.renderAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async undo() {
|
||||||
|
if (this.oldGapX === null || this.oldGapY === null) {
|
||||||
|
console.warn("没有旧间隙可恢复");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await this.execute(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { AddObjectToLayerCommand } from "./ObjectLayerCommands";
|
|||||||
import { ToolCommand } from "./ToolCommands";
|
import { ToolCommand } from "./ToolCommands";
|
||||||
import {
|
import {
|
||||||
findObjectById,
|
findObjectById,
|
||||||
|
findObjectByLayerId,
|
||||||
generateId,
|
generateId,
|
||||||
getObjectZIndex,
|
getObjectZIndex,
|
||||||
insertObjectAtZIndex,
|
insertObjectAtZIndex,
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
} from "../utils/helper";
|
} from "../utils/helper";
|
||||||
import { fabric } from "fabric-with-all";
|
import { fabric } from "fabric-with-all";
|
||||||
import { restoreFabricObject } from "../utils/objectHelper";
|
import { restoreFabricObject } from "../utils/objectHelper";
|
||||||
|
import EventManager from "../utils/event.js";
|
||||||
/**
|
/**
|
||||||
* 添加图层命令
|
* 添加图层命令
|
||||||
*/
|
*/
|
||||||
@@ -36,7 +37,7 @@ export class AddLayerCommand extends Command {
|
|||||||
|
|
||||||
this.insertIndex = options.insertIndex;
|
this.insertIndex = options.insertIndex;
|
||||||
this.oldActiveLayerId = null;
|
this.oldActiveLayerId = null;
|
||||||
this.beforeLayers = [...this.layers.value]; // 备份原图层列表
|
this.beforeLayers = JSON.stringify(this.layers.value); // 备份原图层列表
|
||||||
|
|
||||||
this.options = options.options || {};
|
this.options = options.options || {};
|
||||||
}
|
}
|
||||||
@@ -70,7 +71,7 @@ export class AddLayerCommand extends Command {
|
|||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
// 从图层列表删除该图层
|
// 从图层列表删除该图层
|
||||||
this.layers.value = [...this.beforeLayers];
|
this.layers.value = JSON.parse(this.beforeLayers);
|
||||||
|
|
||||||
// 恢复原活动图层
|
// 恢复原活动图层
|
||||||
this.activeLayerId.value = this.oldActiveLayerId;
|
this.activeLayerId.value = this.oldActiveLayerId;
|
||||||
@@ -143,7 +144,7 @@ export class AddLayerCommand extends Command {
|
|||||||
// 先在一级图层中查找
|
// 先在一级图层中查找
|
||||||
for (let i = 0; i < layers.length; i++) {
|
for (let i = 0; i < layers.length; i++) {
|
||||||
const layer = layers[i];
|
const layer = layers[i];
|
||||||
|
if (layer.isPrintTrimsGroup) continue;
|
||||||
if (layer.id === layerId) {
|
if (layer.id === layerId) {
|
||||||
return {
|
return {
|
||||||
layer: layer,
|
layer: layer,
|
||||||
@@ -251,12 +252,12 @@ export class PasteLayerCommand extends Command {
|
|||||||
(await restoreFabricObject(groupLayer?.clippingMask, this.canvas)) ||
|
(await restoreFabricObject(groupLayer?.clippingMask, this.canvas)) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({
|
clippingMaskFabricObject.set({
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
clippingMaskFabricObject.dirty = true;
|
// clippingMaskFabricObject.dirty = true;
|
||||||
clippingMaskFabricObject.setCoords();
|
clippingMaskFabricObject.setCoords();
|
||||||
// 添加所有对象到画布
|
// 添加所有对象到画布
|
||||||
allObjects.forEach((obj) => {
|
allObjects.forEach((obj) => {
|
||||||
@@ -523,6 +524,7 @@ export class RemoveLayerCommand extends Command {
|
|||||||
this.layerId = options.layerId;
|
this.layerId = options.layerId;
|
||||||
this.activeLayerId = options.activeLayerId;
|
this.activeLayerId = options.activeLayerId;
|
||||||
this.layerManager = options.layerManager || null;
|
this.layerManager = options.layerManager || null;
|
||||||
|
this.IsOnlyLayer = this.layers.value.filter((v => !v.isFixed && !v.isFixedOther && !v.isBackground)).length <= 1
|
||||||
|
|
||||||
// 查找要删除的图层
|
// 查找要删除的图层
|
||||||
this.layerIndex = this.layers.value.findIndex(
|
this.layerIndex = this.layers.value.findIndex(
|
||||||
@@ -599,7 +601,9 @@ export class RemoveLayerCommand extends Command {
|
|||||||
);
|
);
|
||||||
// 从图层列表中删除
|
// 从图层列表中删除
|
||||||
this.layers.value.splice(this.layerIndex, 1);
|
this.layers.value.splice(this.layerIndex, 1);
|
||||||
|
if(this.IsOnlyLayer){
|
||||||
|
this.addCmd = await this.layerManager?.createLayer?.(null, LayerType.EMPTY, {}, false);
|
||||||
|
}
|
||||||
// 如果删除的是当前活动图层,需要更新活动图层
|
// 如果删除的是当前活动图层,需要更新活动图层
|
||||||
if (this.isActiveLayer) {
|
if (this.isActiveLayer) {
|
||||||
// 查找最近的非背景层作为新的活动图层
|
// 查找最近的非背景层作为新的活动图层
|
||||||
@@ -632,6 +636,9 @@ export class RemoveLayerCommand extends Command {
|
|||||||
async undo() {
|
async undo() {
|
||||||
// 恢复图层到原位置
|
// 恢复图层到原位置
|
||||||
if (this.layerIndex !== -1 && this.removedLayer) {
|
if (this.layerIndex !== -1 && this.removedLayer) {
|
||||||
|
if(this.IsOnlyLayer && this.addCmd){
|
||||||
|
this.addCmd?.undo?.();
|
||||||
|
}
|
||||||
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
|
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
|
||||||
|
|
||||||
// 使用优化渲染批处理恢复真实对象到画布
|
// 使用优化渲染批处理恢复真实对象到画布
|
||||||
@@ -649,7 +656,6 @@ export class RemoveLayerCommand extends Command {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
|
||||||
@@ -802,15 +808,23 @@ export class ToggleLayerVisibilityCommand extends Command {
|
|||||||
|
|
||||||
// 切换可见性
|
// 切换可见性
|
||||||
this.layer.visible = !this.layer.visible;
|
this.layer.visible = !this.layer.visible;
|
||||||
|
const ids = [this.layerId];
|
||||||
|
const childLayers = this.layer?.children || [];
|
||||||
|
childLayers.forEach((childLayer) => {
|
||||||
|
childLayer.visible = this.layer.visible;
|
||||||
|
ids.push(childLayer.id);
|
||||||
|
});
|
||||||
|
|
||||||
// 更新画布上图层对象的可见性
|
// 更新画布上图层对象的可见性
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
const layerObjects = this.canvas
|
this.canvas.getObjects().forEach((obj) => {
|
||||||
.getObjects()
|
if (ids.includes(obj.layerId)) {
|
||||||
.filter((obj) => obj.layerId === this.layerId);
|
obj.getObjects?.()?.forEach((item) => {
|
||||||
layerObjects.forEach((obj) => {
|
item.visible = this.layer.visible;
|
||||||
obj.visible = this.layer.visible;
|
});
|
||||||
});
|
obj.visible = this.layer.visible;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// 更新画布上对象的可选择状态
|
// 更新画布上对象的可选择状态
|
||||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||||
@@ -858,23 +872,24 @@ export class ToggleChildLayerVisibilityCommand extends Command {
|
|||||||
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute() {
|
async execute(visible) {
|
||||||
if (!this.childLayer) {
|
if (!this.childLayer) {
|
||||||
throw new Error("找不到要切换可见性的子图层");
|
throw new Error("找不到要切换可见性的子图层");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换可见性
|
// 切换可见性
|
||||||
this.childLayer.visible = !this.childLayer.visible;
|
this.childLayer.visible = typeof visible === "boolean" ? visible : !this.childLayer.visible;
|
||||||
|
|
||||||
// 更新画布上图层对象的可见性
|
// 更新画布上图层对象的可见性
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
const layerObjects = this.canvas
|
this.canvas.getObjects().forEach((obj) => {
|
||||||
.getObjects()
|
if (obj.layerId === this.layerId) {
|
||||||
.filter((obj) => obj.layerId === this.layerId);
|
obj.getObjects?.()?.forEach((item) => {
|
||||||
|
item.visible = this.childLayer.visible;
|
||||||
layerObjects.forEach((obj) => {
|
});
|
||||||
obj.visible = this.childLayer.visible;
|
obj.visible = this.childLayer.visible;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新画布上对象的可选择状态
|
// 更新画布上对象的可选择状态
|
||||||
@@ -1007,9 +1022,8 @@ export class LayerLockCommand extends Command {
|
|||||||
|
|
||||||
// 如果是组图层,递归更新所有子图层
|
// 如果是组图层,递归更新所有子图层
|
||||||
if (
|
if (
|
||||||
layer.type === "group" &&
|
|
||||||
layer.children &&
|
layer.children &&
|
||||||
Array.isArray(layer.children)
|
Array.isArray(layer.children) && layer.children.length > 0
|
||||||
) {
|
) {
|
||||||
layer.children.forEach((child) => {
|
layer.children.forEach((child) => {
|
||||||
this._updateLayerLockState(child, locked);
|
this._updateLayerLockState(child, locked);
|
||||||
@@ -1108,7 +1122,7 @@ export class SetLayerOpacityCommand extends Command {
|
|||||||
|
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
EventManager.emit("object:opacity:execute", this.layerId, this.opacity);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,6 +1144,7 @@ export class SetLayerOpacityCommand extends Command {
|
|||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EventManager.emit("object:opacity:undo", this.layerId, this.opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo() {
|
getInfo() {
|
||||||
@@ -1371,7 +1386,7 @@ export class GroupLayersCommand extends Command {
|
|||||||
// 备份原图层
|
// 备份原图层
|
||||||
this.originalLayers = [...this.layers.value];
|
this.originalLayers = [...this.layers.value];
|
||||||
// 新组ID
|
// 新组ID
|
||||||
this.groupId =
|
this.groupId = options.id ||
|
||||||
generateId("group_layer_") ||
|
generateId("group_layer_") ||
|
||||||
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||||
|
|
||||||
@@ -4276,24 +4291,28 @@ export class RemoveChildLayerCommand extends Command {
|
|||||||
}
|
}
|
||||||
// 恢复子图层到原位置
|
// 恢复子图层到原位置
|
||||||
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
|
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
|
||||||
optimizeCanvasRendering(this.canvas, async () => {
|
await new Promise((resolve) => {
|
||||||
this.originalObjects.forEach((obj) => {
|
optimizeCanvasRendering(this.canvas, async () => {
|
||||||
// 恢复对象到画布
|
this.originalObjects.forEach((obj) => {
|
||||||
this.canvas.add(obj);
|
// 恢复对象到画布
|
||||||
// 恢复对象的图层信息
|
this.canvas.add(obj);
|
||||||
obj.layerId = this.layerId;
|
// 恢复对象的图层信息
|
||||||
obj.layerName = this.removedChild.name;
|
obj.layerId = this.layerId;
|
||||||
obj.setCoords(); // 更新坐标
|
obj.layerName = this.removedChild.name;
|
||||||
});
|
obj.setCoords(); // 更新坐标
|
||||||
|
});
|
||||||
|
|
||||||
// 如果是原活动图层,恢复活动图层
|
// 如果是原活动图层,恢复活动图层
|
||||||
if (this.isActiveLayer) {
|
if (this.isActiveLayer) {
|
||||||
this.activeLayerId.value = this.layerId;
|
this.activeLayerId.value = this.layerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新渲染画布
|
// 重新渲染画布
|
||||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo() {
|
getInfo() {
|
||||||
@@ -4434,3 +4453,90 @@ export class ChildLayerLockCommand extends Command {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 设置图层混合模式
|
||||||
|
*/
|
||||||
|
export class SetLayerCompositeCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({
|
||||||
|
name: "设置图层混合模式",
|
||||||
|
saveState: false,
|
||||||
|
});
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.layers = options.layers;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.layerId = options.layerId;
|
||||||
|
this.newValue = options.newValue;
|
||||||
|
this.oldValue = options.oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(isUndo = false) {
|
||||||
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||||
|
const { object } = findObjectByLayerId(this.canvas, this.layerId);
|
||||||
|
if (!layer || !object) {
|
||||||
|
console.error(`图层${this.layerId}不存在`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// console.log("==========", this.newValue, this.oldValue);
|
||||||
|
const value = isUndo ? this.oldValue : this.newValue;
|
||||||
|
layer.blendMode = value;
|
||||||
|
object.set("globalCompositeOperation", value);
|
||||||
|
this.canvas.renderAll();
|
||||||
|
const event = isUndo ? "object:composite:undo" : "object:composite:execute";
|
||||||
|
EventManager.emit(event, object);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
return this.execute(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置颜色图层颜色
|
||||||
|
*/
|
||||||
|
export class SetColorLayerFillCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({
|
||||||
|
name: "设置颜色图层颜色",
|
||||||
|
saveState: false,
|
||||||
|
});
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.object = options.object;
|
||||||
|
this.layer = this.layerManager?.getLayerById(this.object.layerId);
|
||||||
|
this.newFill = options.newFill;
|
||||||
|
this.oldFill = JSON.parse(JSON.stringify(this.object.fill));
|
||||||
|
this.layer.blendMode = "multiply";
|
||||||
|
this.object.set("globalCompositeOperation", "multiply");
|
||||||
|
this.object.set("originColor", options.originColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(isUndo = false) {
|
||||||
|
if (!this.object) {
|
||||||
|
console.error(`颜色图层不存在`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isVisible = this.layer?.visible;
|
||||||
|
if(!isVisible && this.layer) this.layer.visible = true;
|
||||||
|
const gradient = new fabric.Gradient({
|
||||||
|
type: "linear",
|
||||||
|
gradientUnits: "percentage",
|
||||||
|
...(isUndo ? this.oldFill : this.newFill),
|
||||||
|
});
|
||||||
|
this.object.setFill(gradient);
|
||||||
|
this.canvas.renderAll();
|
||||||
|
await this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
|
||||||
|
this.object.id
|
||||||
|
);
|
||||||
|
if(!isVisible && this.layer) this.layer.visible = false;
|
||||||
|
this.layerManager?.updateLayersObjectsInteractivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
this.execute(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/component/Canvas/CanvasEditor/commands/ObjectCommands.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Command } from "./Command.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象移动命令
|
||||||
|
* 轻量级命令,只记录对象的移动属性变化(位置)
|
||||||
|
*/
|
||||||
|
export class ObjectMoveCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({
|
||||||
|
name: options.name || "对象移动",
|
||||||
|
description: options.description || "移动对象",
|
||||||
|
saveState: false, // 自己管理状态,避免递归
|
||||||
|
});
|
||||||
|
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
this.initPos = options.initPos;
|
||||||
|
this.finalPos = options.finalPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行命令
|
||||||
|
*/
|
||||||
|
async execute() {
|
||||||
|
this.setObjectsPos(this.finalPos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 撤销命令
|
||||||
|
* 应用初始状态
|
||||||
|
*/
|
||||||
|
async undo() {
|
||||||
|
this.setObjectsPos(this.initPos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setObjectsPos(pos) {
|
||||||
|
const objects = this.canvas.getObjects();
|
||||||
|
const arr = typeof pos === "object" ? [pos] : pos;
|
||||||
|
arr.forEach((item) => {
|
||||||
|
const obj = objects.find((o) => o.id === item.id);
|
||||||
|
if(obj) {
|
||||||
|
obj.set({
|
||||||
|
left: item.left,
|
||||||
|
top: item.top,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -234,7 +234,7 @@ export class AddObjectToLayerCommand extends Command {
|
|||||||
parent.clippingMask,
|
parent.clippingMask,
|
||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({ absolutePositioned: true });
|
clippingMaskFabricObject.set({ absolutePositioned: true });
|
||||||
this.fabricObject.clipPath = clippingMaskFabricObject;
|
this.fabricObject.clipPath = clippingMaskFabricObject;
|
||||||
// 标记为脏对象
|
// 标记为脏对象
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
this.layerId
|
this.layerId
|
||||||
);
|
);
|
||||||
this.layer = layer;
|
this.layer = layer;
|
||||||
this.parentLayer = parent;
|
// this.parentLayer = parent;
|
||||||
|
|
||||||
// 新增:如果有父图层,则栅格化父图层及其所有子图层
|
// // 新增:如果有父图层,则栅格化父图层及其所有子图层
|
||||||
if (this.parentLayer) {
|
// if (this.parentLayer) {
|
||||||
this.layer = this.parentLayer;
|
// this.layer = this.parentLayer;
|
||||||
this.layerId = this.parentLayer.id;
|
// this.layerId = this.parentLayer.id;
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 恢复原始图层结构
|
// 恢复原始图层结构
|
||||||
this.layers.value = [...this.originalLayerStructure];
|
this.layers.value = JSON.parse(this.originalLayerStructure);
|
||||||
|
|
||||||
// 恢复原活动图层
|
// 恢复原活动图层
|
||||||
this.activeLayerId.value = this.layerId;
|
this.activeLayerId.value = this.layerId;
|
||||||
@@ -191,7 +191,7 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
_saveOriginalLayerStructure() {
|
_saveOriginalLayerStructure() {
|
||||||
// 只保存相关的图层结构,而不是整个图层数组
|
// 只保存相关的图层结构,而不是整个图层数组
|
||||||
this.originalLayerStructure = JSON.parse(JSON.stringify(this.layers.value));
|
this.originalLayerStructure = JSON.stringify(this.layers.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -517,12 +517,12 @@ export class ExportLayerToImageCommand extends Command {
|
|||||||
this.canvas
|
this.canvas
|
||||||
);
|
);
|
||||||
|
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({
|
clippingMaskFabricObject.set({
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
clippingMaskFabricObject.dirty = true;
|
// clippingMaskFabricObject.dirty = true;
|
||||||
clippingMaskFabricObject.setCoords();
|
clippingMaskFabricObject.setCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { findObjectById } from "../utils/helper";
|
|||||||
import { findLayerRecursively } from "../utils/layerHelper";
|
import { findLayerRecursively } from "../utils/layerHelper";
|
||||||
import { restoreFabricObject } from "../utils/objectHelper";
|
import { restoreFabricObject } from "../utils/objectHelper";
|
||||||
import { Command } from "./Command";
|
import { Command } from "./Command";
|
||||||
|
import EventManager from "../utils/event.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对象变换命令
|
* 对象变换命令
|
||||||
@@ -75,7 +76,7 @@ export class TransformCommand extends Command {
|
|||||||
|
|
||||||
// 触发画布更新
|
// 触发画布更新
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
EventManager.emit("object:modified:execute", targetObject);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ export class TransformCommand extends Command {
|
|||||||
}, 300);
|
}, 300);
|
||||||
// 触发画布更新
|
// 触发画布更新
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
EventManager.emit("object:modified:undo", targetObject);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +168,7 @@ export class TransformCommand extends Command {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (clippingMaskFabricObject) {
|
if (clippingMaskFabricObject) {
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({
|
clippingMaskFabricObject.set({
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ export class CreateTextCommand extends Command {
|
|||||||
// 先在一级图层中查找
|
// 先在一级图层中查找
|
||||||
for (let i = 0; i < layers.length; i++) {
|
for (let i = 0; i < layers.length; i++) {
|
||||||
const layer = layers[i];
|
const layer = layers[i];
|
||||||
|
if (layer.isPrintTrimsGroup) continue;
|
||||||
if (layer.id === layerId) {
|
if (layer.id === layerId) {
|
||||||
return {
|
return {
|
||||||
layer: layer,
|
layer: layer,
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clippingMaskFabricObject.clipPath = null;
|
// clippingMaskFabricObject.clipPath = null;
|
||||||
clippingMaskFabricObject.set({
|
clippingMaskFabricObject.set({
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 图片列表面板 -->
|
|
||||||
<div v-if="showPanel" class="crop-image-overlay" @click.self="close">
|
<div v-if="showPanel" class="crop-image-overlay" @click.self="close">
|
||||||
<div class="crop-image-modal">
|
<div class="crop-image-modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -392,7 +391,7 @@
|
|||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
/* 弹窗遮罩层 */
|
/* 弹窗遮罩层 */
|
||||||
.crop-image-overlay {
|
.crop-image-overlay {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -420,8 +419,8 @@
|
|||||||
.crop-image-modal {
|
.crop-image-modal {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 80%;
|
width: 90%;
|
||||||
height: 80%;
|
height: 90%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
import { ref, nextTick, computed, inject } from "vue";
|
import { ref, nextTick, computed, inject } from "vue";
|
||||||
import { Checkbox } from "ant-design-vue";
|
import { Checkbox } from "ant-design-vue";
|
||||||
import { VueDraggable } from "vue-draggable-plus";
|
import { VueDraggable } from "vue-draggable-plus";
|
||||||
import { isGroupLayer } from "../../utils/layerHelper";
|
import { isGroupLayer, SpecialLayerId } from "../../utils/layerHelper";
|
||||||
|
import { fillToCssStyle, palletToFill, fillToPallet } from "../../utils/helper";
|
||||||
|
import { SetColorLayerFillCommand } from "../../commands/LayerCommands";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
// 设置组件名称,用于递归渲染
|
// 设置组件名称,用于递归渲染
|
||||||
@@ -183,6 +185,9 @@ function handleToggleVisibility() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleToggleLock() {
|
function handleToggleLock() {
|
||||||
|
// 禁用解锁的图层不能操作
|
||||||
|
if (props.layer.isDisableUnlock) return;
|
||||||
|
|
||||||
if (props.isChild) {
|
if (props.isChild) {
|
||||||
// 子图层需要传递父图层ID - 从父级组件获取
|
// 子图层需要传递父图层ID - 从父级组件获取
|
||||||
const parentId = props.layer.parentId || findParentLayerId();
|
const parentId = props.layer.parentId || findParentLayerId();
|
||||||
@@ -348,6 +353,29 @@ function findParentLayerId() {
|
|||||||
console.warn("无法找到图层的父图层:", props.layer.id);
|
console.warn("无法找到图层的父图层:", props.layer.id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const canvasManager = inject('canvasManager');
|
||||||
|
const layerObject = computed(() => {
|
||||||
|
const layer = props.layer;
|
||||||
|
const id = layer.fabricObject?.id || layer.fabricObjects?.[0]?.id || layer.id;
|
||||||
|
return canvasManager.getLayerObjectById(id);
|
||||||
|
});
|
||||||
|
const palletPanel = inject("palletPanel");
|
||||||
|
const clickColor = () => {
|
||||||
|
const fill = layerObject.value.fill;
|
||||||
|
if (fill) {
|
||||||
|
const obj = fillToPallet(fill);
|
||||||
|
palletPanel(obj).then((res) => {
|
||||||
|
const cmd = new SetColorLayerFillCommand({
|
||||||
|
canvas: canvasManager.canvas,
|
||||||
|
layerManager: layerManager,
|
||||||
|
object: layerObject.value,
|
||||||
|
newFill: palletToFill(res),
|
||||||
|
originColor: res,
|
||||||
|
});
|
||||||
|
layerManager.commandManager.execute(cmd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -377,8 +405,8 @@ function findParentLayerId() {
|
|||||||
@contextmenu.prevent="handleContextMenu"
|
@contextmenu.prevent="handleContextMenu"
|
||||||
>
|
>
|
||||||
<!-- 拖拽手柄 -->
|
<!-- 拖拽手柄 -->
|
||||||
<div class="layer-drag-handle" :title="$t('拖拽排序')">
|
<div class="layer-drag-handle" :title="$t('拖拽排序')" v-if="!isHidenDragHandle">
|
||||||
<SvgIcon v-if="!isHidenDragHandle" :name="isChild ? 'CSort' : 'CSort'" :size="32"></SvgIcon>
|
<SvgIcon :name="isChild ? 'CSort' : 'CSort'" :size="32"></SvgIcon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图层头部 -->
|
<!-- 图层头部 -->
|
||||||
@@ -417,9 +445,18 @@ function findParentLayerId() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 颜色图层按钮 -->
|
||||||
|
<div
|
||||||
|
class="layer-color-btn"
|
||||||
|
v-if="layer.id === SpecialLayerId.COLOR"
|
||||||
|
@click.stop="clickColor"
|
||||||
|
:style="{
|
||||||
|
background: fillToCssStyle(layerObject.fill),
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- 图层操作按钮 -->
|
<!-- 图层操作按钮 -->
|
||||||
<div class="layer-actions" v-if="!(isGroupLayerType && !isChild)">
|
<div class="layer-actions" >
|
||||||
<!-- 可见性切换 -->
|
<!-- 可见性切换 -->
|
||||||
<div
|
<div
|
||||||
class="visibility-btn"
|
class="visibility-btn"
|
||||||
@@ -434,7 +471,7 @@ function findParentLayerId() {
|
|||||||
<span
|
<span
|
||||||
v-if="layer.locked"
|
v-if="layer.locked"
|
||||||
class="status-icon locked"
|
class="status-icon locked"
|
||||||
:class="{ disabled: layer.isBackground || layer.isFixed }"
|
:class="{ disabled: layer.isBackground || layer.isFixed || layer.isDisableUnlock || layer.isFixedOther }"
|
||||||
:title="$t('锁定')"
|
:title="$t('锁定')"
|
||||||
@click.stop="handleToggleLock"
|
@click.stop="handleToggleLock"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:is-child="isChild"
|
:is-child="isChild"
|
||||||
:is-active="layer.id === activeLayerId"
|
:is-active="layer.id === activeLayerId"
|
||||||
:is-selected="isLayerSelected(layer.id)"
|
:is-selected="isLayerSelected(layer.id)"
|
||||||
:is-multi-select-mode="isMultiSelectMode"
|
:is-multi-select-mode="isMultiSelectMode && !(layer.isPrintTrims || layer.isPrintTrimsGroup)"
|
||||||
:is-editing="editingLayerId === layer.id"
|
:is-editing="editingLayerId === layer.id"
|
||||||
:editing-name="editingLayerName"
|
:editing-name="editingLayerName"
|
||||||
:can-delete="
|
:can-delete="
|
||||||
@@ -296,7 +296,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:expanded-group-ids="expandedGroupIds"
|
:expanded-group-ids="expandedGroupIds"
|
||||||
@click="(...args) => forwardEvent('layer-click', ...args)"
|
@click="(...args) => forwardEvent('layer-click', ...args)"
|
||||||
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
@context-menu="(...args) => !(layer.isPrintTrims || layer.isPrintTrimsGroup) && forwardEvent('context-menu', ...args)"
|
||||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||||
@@ -385,17 +385,10 @@ const canDeleteComputed = computed(() => {
|
|||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
// 从父组件的样式文件中继承相关样式
|
// 从父组件的样式文件中继承相关样式
|
||||||
.layers-list {
|
.layers-list {
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.sortable-layers {
|
.sortable-layers {
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .layer-group {
|
|
||||||
// // margin-bottom: 1px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.child-layers {
|
.child-layers {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
|||||||
@@ -81,14 +81,14 @@ const fillColorRef = ref(null);
|
|||||||
// 计算属性:可排序的根级图层(排除背景层和固定层)
|
// 计算属性:可排序的根级图层(排除背景层和固定层)
|
||||||
const sortableRootLayers = computed(() => {
|
const sortableRootLayers = computed(() => {
|
||||||
if (!layers) return [];
|
if (!layers) return [];
|
||||||
return layers.value.filter((layer) => !layer.parentId && !layer.isFixed && !layer.isBackground);
|
return layers.value.filter((layer) => !layer.parentId && !layer.isFixed && !layer.isBackground && !layer.isFixedOther);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性:不可排序的固定图层(背景层和固定层)
|
// 计算属性:不可排序的固定图层(背景层和固定层)
|
||||||
const fixedLayers = computed(() => {
|
const fixedLayers = computed(() => {
|
||||||
if (!layers) return [];
|
if (!layers) return [];
|
||||||
return layers.value.filter((layer) => {
|
return layers.value.filter((layer) => {
|
||||||
if (props.showFixedLayer) return !layer.parentId && (layer.isFixed || layer.isBackground);
|
if (props.showFixedLayer) return !layer.parentId && (layer.isFixed || layer.isBackground || layer.isFixedOther);
|
||||||
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
|
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -576,7 +576,7 @@ function handleLayerClick(layer, event) {
|
|||||||
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
|
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
|
||||||
toggleLayerSelection(layer, event);
|
toggleLayerSelection(layer, event);
|
||||||
} else {
|
} else {
|
||||||
lastSelectLayerId.value = layer.id; // 更新最后选中的图层ID
|
if(!layer.isPrintTrimsGroup) lastSelectLayerId.value = layer.id; // 更新最后选中的图层ID
|
||||||
// 普通点击:进入单选模式
|
// 普通点击:进入单选模式
|
||||||
// selectedLayerIds.value = [layer.id];
|
// selectedLayerIds.value = [layer.id];
|
||||||
// isMultiSelectMode.value = false;
|
// isMultiSelectMode.value = false;
|
||||||
@@ -596,7 +596,7 @@ function handleLayerClick(layer, event) {
|
|||||||
layerManager?.updateLayersObjectsInteractivity();
|
layerManager?.updateLayersObjectsInteractivity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
if(!layer.isPrintTrimsGroup) lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,7 +999,7 @@ function buildChildLayerContextMenuItems(childLayer) {
|
|||||||
{
|
{
|
||||||
label: childLayer.locked ? "解锁图层" : "锁定图层",
|
label: childLayer.locked ? "解锁图层" : "锁定图层",
|
||||||
icon: childLayer.locked ? "CUnLock" : "CLock",
|
icon: childLayer.locked ? "CUnLock" : "CLock",
|
||||||
disabled: childLayer.isBackground || childLayer.isFixed,
|
disabled: childLayer.isBackground || childLayer.isFixed || childLayer.isDisableUnlock,
|
||||||
action: () => toggleChildLayerLock(childLayer.id),
|
action: () => toggleChildLayerLock(childLayer.id),
|
||||||
},
|
},
|
||||||
// 显示/隐藏
|
// 显示/隐藏
|
||||||
@@ -1240,6 +1240,12 @@ async function handleCrossLevelMove(moveData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const layer = findLayerRecursively(layers.value, layerId).layer;
|
||||||
|
const toLayer = findLayerRecursively(layers.value, toParentId).layer;
|
||||||
|
if(layer?.isPrintTrims || layer?.isPrintTrimsGroup || toLayer?.isPrintTrims || toLayer?.isPrintTrimsGroup) {
|
||||||
|
console.warn("当前图层不可移动到外部");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 如果有命令管理器,使用命令模式
|
// 如果有命令管理器,使用命令模式
|
||||||
if (commandManager) {
|
if (commandManager) {
|
||||||
console.log("📝 使用命令模式执行跨层级移动");
|
console.log("📝 使用命令模式执行跨层级移动");
|
||||||
@@ -1593,47 +1599,48 @@ async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex
|
|||||||
<small>{{ $t('Canvas.Hint') }}</small>
|
<small>{{ $t('Canvas.Hint') }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="layers-list-container">
|
||||||
<!-- 图层列表组件 -->
|
<!-- 图层列表组件 -->
|
||||||
<LayersList
|
<LayersList
|
||||||
:layers="layers"
|
:layers="layers"
|
||||||
:active-layer-id="activeLayerId"
|
:active-layer-id="activeLayerId"
|
||||||
:sortable-root-layers="sortableRootLayers"
|
:sortable-root-layers="sortableRootLayers"
|
||||||
:fixed-layers="fixedLayers"
|
:fixed-layers="fixedLayers"
|
||||||
:selected-layer-ids="selectedLayerIds"
|
:selected-layer-ids="selectedLayerIds"
|
||||||
:is-multi-select-mode="isMultiSelectMode"
|
:is-multi-select-mode="isMultiSelectMode"
|
||||||
:editing-layer-id="editingLayerId"
|
:editing-layer-id="editingLayerId"
|
||||||
:editing-layer-name="editingLayerName"
|
:editing-layer-name="editingLayerName"
|
||||||
:thumbnail-manager="thumbnailManager"
|
:thumbnail-manager="thumbnailManager"
|
||||||
:expanded-group-ids="expandedGroupIds"
|
:expanded-group-ids="expandedGroupIds"
|
||||||
:isChild="false"
|
:isChild="false"
|
||||||
group-name="layers-root"
|
group-name="layers-root"
|
||||||
@layer-click="handleLayerClick"
|
@layer-click="handleLayerClick"
|
||||||
@layer-double-click="handleLayerDoubleClick"
|
@layer-double-click="handleLayerDoubleClick"
|
||||||
@context-menu="showContextMenu"
|
@context-menu="showContextMenu"
|
||||||
@checkbox-change="handleCheckboxClick"
|
@checkbox-change="handleCheckboxClick"
|
||||||
@toggle-visibility="toggleLayerVisibility"
|
@toggle-visibility="toggleLayerVisibility"
|
||||||
@toggle-lock="toggleSelectedLayersLockByLayer"
|
@toggle-lock="toggleSelectedLayersLockByLayer"
|
||||||
@delete="removeLayer"
|
@delete="removeLayer"
|
||||||
@edit-confirm="confirmEdit"
|
@edit-confirm="confirmEdit"
|
||||||
@edit-cancel="cancelEdit"
|
@edit-cancel="cancelEdit"
|
||||||
@edit-keydown="handleEditKeydown"
|
@edit-keydown="handleEditKeydown"
|
||||||
@touch-start="handleTouchStart"
|
@touch-start="handleTouchStart"
|
||||||
@touch-move="handleTouchMove"
|
@touch-move="handleTouchMove"
|
||||||
@touch-end="handleTouchEnd"
|
@touch-end="handleTouchEnd"
|
||||||
@update:editing-name="editingLayerName = $event"
|
@update:editing-name="editingLayerName = $event"
|
||||||
@root-layers-sort="handleRootLayersSort"
|
@root-layers-sort="handleRootLayersSort"
|
||||||
@child-layers-sort="handleChildLayersSort"
|
@child-layers-sort="handleChildLayersSort"
|
||||||
@cross-level-move="handleCrossLevelMove"
|
@cross-level-move="handleCrossLevelMove"
|
||||||
@select-child-layer="selectChildLayer"
|
@select-child-layer="selectChildLayer"
|
||||||
@start-child-layer-edit="startChildLayerEdit"
|
@start-child-layer-edit="startChildLayerEdit"
|
||||||
@child-context-menu="showChildLayerContextMenu"
|
@child-context-menu="showChildLayerContextMenu"
|
||||||
@toggle-group-expanded="toggleGroupExpanded"
|
@toggle-group-expanded="toggleGroupExpanded"
|
||||||
@toggle-child-visibility="toggleChildLayerVisibility"
|
@toggle-child-visibility="toggleChildLayerVisibility"
|
||||||
@toggle-child-lock="toggleChildLayerLock"
|
@toggle-child-lock="toggleChildLayerLock"
|
||||||
@delete-child="deleteChildLayer"
|
@delete-child="deleteChildLayer"
|
||||||
@rename-child="renameChildLayer"
|
@rename-child="renameChildLayer"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<!-- 固定层(背景层和固定层) -->
|
<!-- 固定层(背景层和固定层) -->
|
||||||
<div v-if="fixedLayers.length > 0" class="fixed-layers">
|
<div v-if="fixedLayers.length > 0" class="fixed-layers">
|
||||||
<!-- 遍历固定层 -->
|
<!-- 遍历固定层 -->
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
overflow-y: auto;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// max-height: 70vh;
|
// max-height: 70vh;
|
||||||
|
overflow: hidden;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,12 +161,12 @@
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.layers-list-container{
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
// 图层列表
|
// 图层列表
|
||||||
.layers-list {
|
.layers-list {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图层项样式
|
// 图层项样式
|
||||||
@@ -340,6 +340,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layer-color-btn{
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
// 图层操作
|
// 图层操作
|
||||||
.layer-actions {
|
.layer-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ async function prepareForLiquify(targetObj) {
|
|||||||
}
|
}
|
||||||
updateAllParams();
|
updateAllParams();
|
||||||
|
|
||||||
console.log("液化环境准备完成");
|
console.log("液化环境准备完成",compositeCommand);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("准备液化环境失败:", error);
|
console.error("准备液化环境失败:", error);
|
||||||
@@ -1614,6 +1614,7 @@ function close() {
|
|||||||
*/
|
*/
|
||||||
function startPressTimer() {
|
function startPressTimer() {
|
||||||
if (pressTimer.value) return;
|
if (pressTimer.value) return;
|
||||||
|
if (currentMode.value === compositeCommand.value.liquifyManager.enhancedManager.modes.PUSH) return;
|
||||||
|
|
||||||
pressTimer.value = setInterval(() => {
|
pressTimer.value = setInterval(() => {
|
||||||
// 计算按压持续时间
|
// 计算按压持续时间
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 颜色选择器模板 -->
|
||||||
|
<div v-show="showPanel" class="pallet-overlay" @click.self="close">
|
||||||
|
<div class="pallet-modal">
|
||||||
|
<!-- <div class="modal-header">
|
||||||
|
<h3></h3>
|
||||||
|
<button class="close-btn" @click="close">×</button>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<pallet
|
||||||
|
v-if="showPanel"
|
||||||
|
:selectColor="selectColor"
|
||||||
|
@selectUplpadColor="selectUplpadColor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="image-count" @click="close">
|
||||||
|
{{ $t("Canvas.close") }}
|
||||||
|
</div>
|
||||||
|
<div class="image-submit gallery_btn" @click="confirm">
|
||||||
|
{{ $t("Canvas.confirm") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
defineProps,
|
||||||
|
onDeactivated,
|
||||||
|
reactive,
|
||||||
|
onMounted,
|
||||||
|
defineExpose,
|
||||||
|
nextTick,
|
||||||
|
onUnmounted,
|
||||||
|
} from "vue";
|
||||||
|
import pallet from "./pallet.vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps([]);
|
||||||
|
const { t } = useI18n();
|
||||||
|
var resolveFn: (value: any) => void;
|
||||||
|
const showPanel = ref(false);
|
||||||
|
const open = (obj = {}) => {
|
||||||
|
selectColor.value = JSON.parse(JSON.stringify(obj));
|
||||||
|
showPanel.value = true;
|
||||||
|
return new Promise((resolve) => (resolveFn = resolve));
|
||||||
|
};
|
||||||
|
const close = () => {
|
||||||
|
showPanel.value = false;
|
||||||
|
};
|
||||||
|
//提交选中的T图片
|
||||||
|
const confirm = () => {
|
||||||
|
close();
|
||||||
|
resolveFn && resolveFn(JSON.parse(JSON.stringify(selectColor.value)));
|
||||||
|
};
|
||||||
|
const selectColor = ref({});
|
||||||
|
const selectUplpadColor = (item: any) => {
|
||||||
|
selectColor.value = JSON.parse(JSON.stringify(item));
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
/* 弹窗遮罩层 */
|
||||||
|
.pallet-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1001;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗主体 */
|
||||||
|
.pallet-modal {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 95%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||||
|
animation: modalSlideUp 0.3s ease;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalSlideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗头部 */
|
||||||
|
.modal-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: all 0.2s;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: #333;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗内容 */
|
||||||
|
.modal-content {
|
||||||
|
width: 35rem;
|
||||||
|
// max-width: 240px;
|
||||||
|
margin: 10px 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗底部 */
|
||||||
|
.modal-footer {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
// border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
> .image-submit {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,666 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pallet" ref="palletRef">
|
||||||
|
<div class="palletColo" @click="openPallet">
|
||||||
|
<div v-show="!selectColor.gradient" class="palletBackColor" :title="selectColor.name" :style="{'background-color':selectColor.hex}">
|
||||||
|
{{ selectColor.hex }}
|
||||||
|
</div>
|
||||||
|
<div v-show="selectColor.gradient" class="palletBackColor" :style="{'background-image':`linear-gradient(${selectColor.gradient?.angle}deg,${setGradient(selectColor.gradient)})`}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="palletBox">
|
||||||
|
<div class="color_setting_block" @click.stop>
|
||||||
|
<Chrome class="chrome_color" v-model="color_"></Chrome>
|
||||||
|
<div class="color_setting_operateSingle">
|
||||||
|
<div class="color_setting_btn" :class="{active:!color?.gradient?.gradientShow}">{{ $t('ColorboardUpload.Single') }}</div>
|
||||||
|
<a-switch :checked="color?.gradient?.gradientShow" @click="setOperate"/>
|
||||||
|
<div class="color_setting_btn" :class="{active:color?.gradient?.gradientShow}">{{ $t('ColorboardUpload.Gradual') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="color_setting_operate" v-if="color?.gradient?.gradientShow">
|
||||||
|
<div class="color_setting_operate_item color_setting_operate_control">
|
||||||
|
<div class="operate_item_box">
|
||||||
|
<div>{{ $t('ColorboardUpload.Alignment') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="operate_item_box operate_item_angle">
|
||||||
|
<div class="operate_item_angle_box" @mousedown="mousedownGradientAngle(getMousePosition($event,false))" @touchstart="mousedownGradientAngle(getMousePosition($event,true))">
|
||||||
|
<div :style="{'transform':`rotate(${color.gradient.angle}deg)`}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="operate_item_box operate_item_delete">
|
||||||
|
<i class="fi fi-rr-trash" @click="deleteGradientItem"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="color_setting_operate_item color_setting_operate_input">
|
||||||
|
<div class="color_setting_operate_bg" @click="addGradient($event)" :style="{'background-image':color?.gradient?`linear-gradient(90deg,${setGradient(color.gradient)})`:'none'}">
|
||||||
|
</div>
|
||||||
|
<div v-for="item,index in color.gradient.gradientList" :key="item" class="color_setting_operate_btn" :class="{'active':index == color.gradient.selectIndex}" :style="{'left':item.left,'background-color':`rgba(${item.rgba.r},${item.rgba.g},${item.rgba.b},${item.rgba.a})`}" @mousedown="mousedownGradient(getMousePosition($event,false),item,index,)" @touchstart="mousedownGradient(getMousePosition($event,true),item,index,)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent,computed,ref,watch,nextTick,onMounted,onUnmounted,toRefs, reactive} from 'vue'
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { message,Upload} from 'ant-design-vue';
|
||||||
|
import { Sketch, Chrome} from '@ans1998/vue3-color'
|
||||||
|
import { getMousePosition } from "@/tool/mdEvent";
|
||||||
|
import { rgbaToHex } from "@/tool/util"
|
||||||
|
import { color } from 'echarts/core';
|
||||||
|
export default defineComponent({
|
||||||
|
components:{
|
||||||
|
Chrome,
|
||||||
|
},
|
||||||
|
props:{
|
||||||
|
selectColor:{
|
||||||
|
type:Object,
|
||||||
|
default:()=>{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits:['selectUplpadColor'],
|
||||||
|
setup(props,{emit}) {
|
||||||
|
const {t} = useI18n()
|
||||||
|
const store = useStore();
|
||||||
|
const palletData = reactive({
|
||||||
|
palletShow: true,
|
||||||
|
palletList:[],
|
||||||
|
color_:{} as any,
|
||||||
|
color:{} as any,
|
||||||
|
updataSelectColorTime:null as any,
|
||||||
|
gradient:{
|
||||||
|
gradientList:[
|
||||||
|
{
|
||||||
|
rgba:{
|
||||||
|
r:117,
|
||||||
|
g:119,
|
||||||
|
b:255,
|
||||||
|
a:1,
|
||||||
|
},
|
||||||
|
left:'0%'
|
||||||
|
},{
|
||||||
|
rgba:{
|
||||||
|
r:0,
|
||||||
|
g:222,
|
||||||
|
b:152,
|
||||||
|
a:1,
|
||||||
|
},
|
||||||
|
left:'100%'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
angle:45,
|
||||||
|
selectIndex:-1,
|
||||||
|
gradientShow:false,
|
||||||
|
},
|
||||||
|
setGradient:computed(()=>{
|
||||||
|
return (gradient:any)=>{
|
||||||
|
let gradientStr = ''
|
||||||
|
if(!gradient?.gradientList)return
|
||||||
|
gradient.gradientList.sort((a:any, b:any) => {
|
||||||
|
let aArr = a.left.split('%')[0]
|
||||||
|
let bArr = b.left.split('%')[0]
|
||||||
|
return aArr - bArr;
|
||||||
|
});
|
||||||
|
gradient.gradientList.forEach((item:any,index:any)=>{
|
||||||
|
let str = ','
|
||||||
|
if(gradient.gradientList.length == index+1)str = ''
|
||||||
|
let rgba = item.rgba?item.rgba:{r:255,g:255,b:255}
|
||||||
|
gradientStr += `rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a}) ${item.left}${str}`
|
||||||
|
|
||||||
|
})
|
||||||
|
return `${gradientStr}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
const getpalletListDom = reactive({
|
||||||
|
})
|
||||||
|
const palletRef = ref(null)
|
||||||
|
watch(()=>palletData.color_,(newVal:any)=>{
|
||||||
|
if(!newVal?.rgba?.r)return
|
||||||
|
if(palletData.color?.gradient?.gradientShow){
|
||||||
|
palletData.color.gradient.gradientList[palletData.color.gradient.selectIndex].rgba = {
|
||||||
|
r:newVal.rgba.r,
|
||||||
|
g:newVal.rgba.g,
|
||||||
|
b:newVal.rgba.b,
|
||||||
|
a:newVal.rgba.a,
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
palletData.color = newVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(()=>palletData.color,(newVal:any)=>{
|
||||||
|
if(JSON.stringify(props.selectColor) != JSON.stringify(newVal)){
|
||||||
|
newVal.name = ''
|
||||||
|
newVal.tcx = ''
|
||||||
|
let rgba = [newVal.rgba.r,newVal.rgba.g,newVal.rgba.b]
|
||||||
|
let hex = rgbaToHex(rgba)
|
||||||
|
newVal.hex = hex
|
||||||
|
emit('selectUplpadColor',newVal)
|
||||||
|
}
|
||||||
|
},{deep: true })
|
||||||
|
const setOperate = ()=>{
|
||||||
|
if(!palletData.color.rgba)return message.info(t('DesignDetailAlter.jsContent7'))
|
||||||
|
palletData.color.rgba = palletData.color?.rgba?.r?palletData.color.rgba:{r:0,g:0,b:0,a:1}
|
||||||
|
palletData.gradient.selectIndex = 0
|
||||||
|
palletData.gradient.gradientShow = true
|
||||||
|
if(!palletData.color.gradient){
|
||||||
|
if(palletData.color.rgba.r){
|
||||||
|
palletData.gradient.gradientList[palletData.gradient.selectIndex].rgba = {
|
||||||
|
r:palletData.color.rgba.r,
|
||||||
|
g:palletData.color.rgba.g,
|
||||||
|
b:palletData.color.rgba.b,
|
||||||
|
a:1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
palletData.color.gradient = JSON.parse(JSON.stringify(palletData.gradient))
|
||||||
|
}else{
|
||||||
|
palletData.color.rgba = palletData.color.gradient.gradientList[0].rgba
|
||||||
|
palletData.color.gradient = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const deleteGradientItem = ()=>{
|
||||||
|
if(palletData.color.gradient.gradientList.length <= 2)return
|
||||||
|
palletData.color.gradient.gradientList.splice(palletData.color.gradient.selectIndex,1)
|
||||||
|
}
|
||||||
|
const addGradient = (event:any)=>{
|
||||||
|
let gradientWidth = event.target.clientWidth
|
||||||
|
let left:any = event.offsetX/gradientWidth
|
||||||
|
palletData.color.gradient.gradientList.push({
|
||||||
|
rgba:palletData.color_.rgba,
|
||||||
|
left:left.toFixed(2)*100+'%'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const mousedownGradientAngle = (event:any)=>{
|
||||||
|
// isMoible() true为移动端
|
||||||
|
let domPosition = event.target.getBoundingClientRect()
|
||||||
|
let position = {
|
||||||
|
x:domPosition.x+domPosition.width/2,
|
||||||
|
y:domPosition.y+domPosition.height/2,
|
||||||
|
}
|
||||||
|
let angle
|
||||||
|
let mousedown = function(event:any){
|
||||||
|
let e = getMousePosition(event,false)
|
||||||
|
mouseDownOperation(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
let touchstart = function(event:any){
|
||||||
|
let e = getMousePosition(event,true)
|
||||||
|
mouseDownOperation(e)
|
||||||
|
}
|
||||||
|
let mouseDownOperation = (e:any)=>{
|
||||||
|
let X = position.x
|
||||||
|
let Y = position.y
|
||||||
|
let x = (e.clientX) - X
|
||||||
|
let y = Y -( e.clientY)
|
||||||
|
angle = Math.atan2(x,y)*(180 / Math.PI)
|
||||||
|
// this.colorList[this.selectIndex].gradient = JSON.parse(JSON.stringify(this.gradient))
|
||||||
|
palletData.color.gradient.angle = angle
|
||||||
|
|
||||||
|
}
|
||||||
|
let mouseupGradientAngle = ()=>{
|
||||||
|
window.removeEventListener('touchmove',touchstart)
|
||||||
|
window.removeEventListener('touchend',mouseupGradientAngle)
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove',mousedown)
|
||||||
|
window.removeEventListener('mouseup',mouseupGradientAngle)
|
||||||
|
}
|
||||||
|
window.addEventListener('touchmove',touchstart)
|
||||||
|
window.addEventListener('touchend',mouseupGradientAngle)
|
||||||
|
|
||||||
|
window.addEventListener('mousemove',mousedown)
|
||||||
|
window.addEventListener('mouseup',mouseupGradientAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mousedownGradient = (event:any,item:any,index:number)=>{
|
||||||
|
palletData.color.gradient.selectIndex = index
|
||||||
|
|
||||||
|
|
||||||
|
// this.selectColor = {rgba:gradientRgba,hex:hex} //顔色选择器默认颜色
|
||||||
|
let gradientWidth = (palletRef.value.querySelector('.color_setting_operate_bg') as any).clientWidth
|
||||||
|
let position = {
|
||||||
|
x:event.clientX,
|
||||||
|
left:event.target.style.left?event.target.style.left.split('%')[0]:0
|
||||||
|
}
|
||||||
|
let mousedown = function(event:any){
|
||||||
|
let e = getMousePosition(event,false)
|
||||||
|
mousedownGradient(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
let touchstart = function(event:any){
|
||||||
|
let e = getMousePosition(event,true)
|
||||||
|
mousedownGradient(e)
|
||||||
|
}
|
||||||
|
let mousedownGradient = (e:any)=>{
|
||||||
|
let left = ((e.clientX) - position.x)/gradientWidth*100+Number(position.left)
|
||||||
|
left = (left<0?0:left>100?100:left)
|
||||||
|
item.left = left+'%'
|
||||||
|
}
|
||||||
|
|
||||||
|
let mouseupGradientAngle = ()=>{
|
||||||
|
window.removeEventListener('touchmove',touchstart)
|
||||||
|
window.removeEventListener('touchend',mouseupGradientAngle)
|
||||||
|
window.removeEventListener('mousemove',mousedown)
|
||||||
|
window.removeEventListener('mouseup',mouseupGradientAngle)
|
||||||
|
}
|
||||||
|
window.addEventListener('touchmove',touchstart)
|
||||||
|
window.addEventListener('touchend',mouseupGradientAngle)
|
||||||
|
|
||||||
|
window.addEventListener('mousemove',mousedown)
|
||||||
|
window.addEventListener('mouseup',mouseupGradientAngle)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
const selectImgItem = ()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
const openPallet = ()=>{
|
||||||
|
if(palletData.palletShow && props.selectColor?.rgba?.r){
|
||||||
|
if(props.selectColor.gradient){
|
||||||
|
palletData.color_.rgba = props.selectColor.gradient.gradientList[0].rgba
|
||||||
|
}else{
|
||||||
|
palletData.color_ = JSON.parse(JSON.stringify(props.selectColor))
|
||||||
|
palletData.gradient.gradientShow = false
|
||||||
|
}
|
||||||
|
|
||||||
|
palletData.color = JSON.parse(JSON.stringify(props.selectColor))
|
||||||
|
}else{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 点击外部区域关闭颜色选择器
|
||||||
|
const handleClickOutside = (event: Event) => {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
const colorSettingBlock = palletRef.value.querySelector('.color_setting_block');
|
||||||
|
const palletColo = palletRef.value.querySelector('.palletColo');
|
||||||
|
|
||||||
|
// 如果点击的是 .palletColo 或 .color_setting_block 内部,则不关闭
|
||||||
|
if (palletData.palletShow && colorSettingBlock &&
|
||||||
|
!colorSettingBlock.contains(target) &&
|
||||||
|
!palletColo?.contains(target)) {
|
||||||
|
palletData.palletShow = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
// 添加点击外部区域监听器
|
||||||
|
// document.addEventListener('click', handleClickOutside);
|
||||||
|
|
||||||
|
nextTick().then(()=>{
|
||||||
|
const backIcon = document.createElement('div');
|
||||||
|
backIcon.classList.add('vc-sketch-color-wrap')
|
||||||
|
let dropperDom = palletRef.value.getElementsByClassName('vc-chrome-fields-wrap')[0]
|
||||||
|
dropperDom.appendChild(backIcon);
|
||||||
|
backIcon.addEventListener('click',async ()=>{
|
||||||
|
try {
|
||||||
|
const dropper = new EyeDropper();
|
||||||
|
const result = await dropper.open();
|
||||||
|
let hex = result.sRGBHex.replace("#", "");
|
||||||
|
// 将十六进制颜色码拆分成红、绿、蓝三个部分
|
||||||
|
const r = parseInt(hex.substring(0, 2), 16);
|
||||||
|
const g = parseInt(hex.substring(2, 4), 16);
|
||||||
|
const b = parseInt(hex.substring(4, 6), 16);
|
||||||
|
palletData.color = {rgba:{r:r,g:g,b:b,a:1},hex:result.sRGBHex}
|
||||||
|
// return `rgb(${r}, ${g}, ${b})`;
|
||||||
|
// box.style.backgroundColor = label.textContent = result.sRGBHex;
|
||||||
|
} catch (e) {
|
||||||
|
message.info(t('DesignDetailAlter.jsContent1'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
openPallet();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(()=>{
|
||||||
|
// 清理事件监听器
|
||||||
|
// document.removeEventListener('click', handleClickOutside);
|
||||||
|
})
|
||||||
|
return{
|
||||||
|
...toRefs(palletData),
|
||||||
|
...toRefs(getpalletListDom),
|
||||||
|
palletRef,
|
||||||
|
openPallet,
|
||||||
|
selectImgItem,
|
||||||
|
setOperate,
|
||||||
|
deleteGradientItem,
|
||||||
|
addGradient,
|
||||||
|
mousedownGradientAngle,
|
||||||
|
mousedownGradient,
|
||||||
|
getMousePosition,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.pallet{
|
||||||
|
// position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
user-select: none;
|
||||||
|
> .palletColo{
|
||||||
|
width: 100%;
|
||||||
|
height: 7rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: .5rem .6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
> .palletBackColor{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .palletBox{
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
> .color_setting_block{
|
||||||
|
margin: auto;
|
||||||
|
background: linear-gradient(70deg, #eee4f3, #f3f4e6);
|
||||||
|
width: 100%;
|
||||||
|
// border-radius: calc(1rem*1.2);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 2px 2px 8px rgba(0,0,0,.3);
|
||||||
|
.vc-chrome{
|
||||||
|
background: rgba(0,0,0,0);
|
||||||
|
box-shadow:none;
|
||||||
|
}
|
||||||
|
:deep(.chrome_color){
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.vc-chrome-saturation-wrap{
|
||||||
|
width: 30rem;
|
||||||
|
height: 30rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.vc-saturation-pointer{
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.vc-chrome-body{
|
||||||
|
padding: 0;
|
||||||
|
width: 90%;
|
||||||
|
margin: 2 auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(0,0,0,0);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
// display: none;
|
||||||
|
.vc-chrome-fields-wrap{
|
||||||
|
margin-top: 5%;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
.vc-chrome-toggle-btn{
|
||||||
|
width: calc(3.2rem*1.2);
|
||||||
|
.vc-chrome-toggle-icon{
|
||||||
|
height: auto;
|
||||||
|
margin-right: calc(-0.4rem*1.2);
|
||||||
|
margin-top: calc(0rem*1.2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
svg{
|
||||||
|
width: calc(2.4rem*1.2) !important;
|
||||||
|
height: calc(2.4rem*1.2) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-fields{
|
||||||
|
.vc-chrome-field{
|
||||||
|
padding-left: calc(.6rem*1.2);
|
||||||
|
}
|
||||||
|
.vc-input__label{
|
||||||
|
font-size: calc(1.6rem*1.2);
|
||||||
|
}
|
||||||
|
.vc-input__input{
|
||||||
|
font-size: 2rem;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-upload-list{
|
||||||
|
|
||||||
|
}
|
||||||
|
.vc-sketch-color-wrap{
|
||||||
|
background-image: url(@/assets/images/homePage/dropper.png);
|
||||||
|
background-size: 3rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
padding: calc(.7rem*1.2);
|
||||||
|
border: 1px solid;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2rem;
|
||||||
|
right: 0rem;
|
||||||
|
border-radius: calc(.5rem*1.2);
|
||||||
|
|
||||||
|
}
|
||||||
|
.vc-chrome-fields{
|
||||||
|
.vc-input__label{
|
||||||
|
margin-top: calc(1rem*1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-fields:nth-child(2){
|
||||||
|
>:last-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-fields:nth-child(3){
|
||||||
|
>:last-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-controls{
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
.vc-chrome-color-wrap{
|
||||||
|
// width: 3.6rem*1.2);
|
||||||
|
margin-left: calc(2rem*1.2);
|
||||||
|
width: auto;
|
||||||
|
.vc-chrome-active-color{
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.vc-chrome-active-color,.vc-checkerboard{
|
||||||
|
width: calc(3rem*1.2);
|
||||||
|
height: calc(3rem*1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-hue-wrap,.vc-chrome-alpha-wrap{
|
||||||
|
.vc-hue{
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
.vc-alpha{
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
height: 2rem;
|
||||||
|
margin: 0;
|
||||||
|
.vc-hue-pointer{
|
||||||
|
transform: translateX(-1.25rem);
|
||||||
|
}
|
||||||
|
.vc-hue-picker{
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(0px,-3px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-alpha-wrap{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.vc-chrome-saturation-wrap .vc-saturation-circle{
|
||||||
|
width: calc(1rem*1.2);
|
||||||
|
height: calc(1rem*1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_block{
|
||||||
|
// margin-top: calc(1rem;
|
||||||
|
// display: flex;
|
||||||
|
// justify-content: space-between;
|
||||||
|
// font-size: calc(1.6rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 5%;
|
||||||
|
padding-bottom: 5%;
|
||||||
|
margin: calc(0.5rem*1.2) auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.color_right{
|
||||||
|
width: 13rem;
|
||||||
|
font-size: calc(1.2rem*1.2);
|
||||||
|
color: #666666;
|
||||||
|
.color_rgb_block{
|
||||||
|
display: flex;
|
||||||
|
.rgb_item{
|
||||||
|
margin-left: calc(.2rem*1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_left{
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(153, 153, 153);
|
||||||
|
}
|
||||||
|
.color_right,.color_left{
|
||||||
|
>div{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.color_HEX_block,.color_rgb_block{
|
||||||
|
padding: .25rem .6rem;
|
||||||
|
box-shadow: inset 0 0 0 1px #ccc;
|
||||||
|
border-radius: .5rem;
|
||||||
|
justify-content: space-around;
|
||||||
|
text-transform:uppercase;
|
||||||
|
.color_block_bg{
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
// margin-right: .5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_block_bg{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.color_setting_operateSingle{
|
||||||
|
text-align: center;
|
||||||
|
margin: 1rem 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
.color_setting_btn{
|
||||||
|
margin: 0 1rem;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
&.active{
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_setting_operate{
|
||||||
|
*{
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.color_setting_operate_item{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
.operate_item_box{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_setting_operate_control{
|
||||||
|
.operate_item_delete,.operate_item_angle{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.operate_item_delete{
|
||||||
|
i{
|
||||||
|
display: flex;
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.operate_item_angle{
|
||||||
|
.operate_item_angle_box{
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
border: solid 2px #000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
>div{
|
||||||
|
height: 100%;
|
||||||
|
width: 1rem;
|
||||||
|
position: relative;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
>div::before{
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: 0.2rem;
|
||||||
|
left: 0;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_setting_operate_input{
|
||||||
|
width: 80%;
|
||||||
|
// padding: 0 10%;
|
||||||
|
margin: 1.2rem 10%;
|
||||||
|
border-radius: 10%;
|
||||||
|
position: relative;
|
||||||
|
height: 2.5rem;
|
||||||
|
.color_setting_operate_bg{
|
||||||
|
border-radius: .5rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 2.5rem;
|
||||||
|
background: #fff;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_setting_operate_btn{
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
left: 0;
|
||||||
|
width: 1rem;
|
||||||
|
height: 110%;
|
||||||
|
border: .2rem solid;
|
||||||
|
border-radius: .5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: content-box;
|
||||||
|
z-index: 2;
|
||||||
|
&.active{
|
||||||
|
border: .3rem solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.color_setting_operate_btn:hover{
|
||||||
|
border: .3rem solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div
|
||||||
|
class="part-selector-toolbar"
|
||||||
|
v-if="visible"
|
||||||
|
:class="{ active: !closePanel }"
|
||||||
|
>
|
||||||
|
<div class="btn" @click="setClosePanel">
|
||||||
|
<i class="fi fi-br-angle-left"></i>
|
||||||
|
</div>
|
||||||
|
<!-- 顶部选区类型工具栏 -->
|
||||||
|
<div class="toolbar-section">
|
||||||
|
<div class="toolbar-header">
|
||||||
|
<div class="header-title">
|
||||||
|
{{ t("Canvas.GarmentPartSelector") }}
|
||||||
|
</div>
|
||||||
|
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
||||||
|
<div class="tip">
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="/src/assets/images/canvas/shubiao-l.png"
|
||||||
|
/>
|
||||||
|
<span>Left Click: Add</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="/src/assets/images/canvas/shubiao-r.png"
|
||||||
|
/>
|
||||||
|
<span>Right Click: Remove</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tool-types">
|
||||||
|
<div
|
||||||
|
v-for="item in toolList"
|
||||||
|
:key="item.type"
|
||||||
|
:class="[
|
||||||
|
'tool-btn',
|
||||||
|
{ active: toolType === item.type },
|
||||||
|
]"
|
||||||
|
@click="setPartType(item.type)"
|
||||||
|
>
|
||||||
|
<svg-icon :name="item.icon" :size="item.size" />
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分割线 -->
|
||||||
|
<div class="toolbar-divider"></div>
|
||||||
|
|
||||||
|
<!-- 底部选区操作工具栏 -->
|
||||||
|
<div class="tool-actions">
|
||||||
|
<div class="action-btn" @click="onCreate">
|
||||||
|
<svg-icon name="CPaste" size="16" />
|
||||||
|
<span class="btn-text">{{
|
||||||
|
$t("Canvas.creation")
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="action-btn" @click="onCopyCreate">
|
||||||
|
<svg-icon name="CCut" size="26" />
|
||||||
|
<span class="btn-text">{{
|
||||||
|
$t("Canvas.CreateAndCopy")
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import {
|
||||||
|
CreateSelectionCommand,
|
||||||
|
InvertSelectionCommand,
|
||||||
|
FeatherSelectionCommand,
|
||||||
|
FillSelectionCommand,
|
||||||
|
} from "../commands/SelectionCommands";
|
||||||
|
import { ToolCommand } from "../commands/ToolCommands";
|
||||||
|
import {
|
||||||
|
LassoCutoutCommand,
|
||||||
|
ClearSelectionCommand,
|
||||||
|
// CutSelectionToNewLayerCommand,
|
||||||
|
} from "../commands/LassoCutoutCommand";
|
||||||
|
|
||||||
|
import { OperationType } from "../utils/layerHelper";
|
||||||
|
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
||||||
|
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
canvas: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
commandManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
partManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
partManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
layerManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
toolManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
activeTool: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const visible = ref(false);
|
||||||
|
const toolType = ref(OperationType.PART);
|
||||||
|
//打开隐藏操作面板
|
||||||
|
const closePanel = ref(false);
|
||||||
|
const setClosePanel = () => {
|
||||||
|
closePanel.value = !closePanel.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toolList = [
|
||||||
|
{
|
||||||
|
type: OperationType.PART,
|
||||||
|
label: "Point Selection",
|
||||||
|
icon: "CPoint",
|
||||||
|
size: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_RECTANGLE,
|
||||||
|
label: "Marquee Selection",
|
||||||
|
icon: "CMarquee",
|
||||||
|
size: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_BRUSH,
|
||||||
|
label: "Brush Selection",
|
||||||
|
icon: "CBrush2",
|
||||||
|
size: "16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OperationType.PART_ERASER,
|
||||||
|
label: "Erase",
|
||||||
|
icon: "CEraser2",
|
||||||
|
size: "22",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
|
||||||
|
// 监听 activeTool 变化
|
||||||
|
watch(
|
||||||
|
() => props.activeTool,
|
||||||
|
(newTool) => {
|
||||||
|
// 当工具为LASSO或AREA类型时显示选区面板
|
||||||
|
const selectionTools = [
|
||||||
|
OperationType.PART,
|
||||||
|
OperationType.PART_RECTANGLE,
|
||||||
|
OperationType.PART_BRUSH,
|
||||||
|
OperationType.PART_ERASER,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (selectionTools.includes(newTool)) {
|
||||||
|
show();
|
||||||
|
// 根据工具类型设置选区类型
|
||||||
|
toolType.value = newTool;
|
||||||
|
|
||||||
|
// 更新选区管理器的选区类型
|
||||||
|
// if (props.partManager) {
|
||||||
|
// props.partManager.setPartType(toolType.value);
|
||||||
|
// props.partManager.setupPartEvents();
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示面板
|
||||||
|
*/
|
||||||
|
function show() {
|
||||||
|
visible.value = true;
|
||||||
|
closePanel.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭面板
|
||||||
|
*/
|
||||||
|
function close() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选区类型
|
||||||
|
*/
|
||||||
|
function setPartType(type) {
|
||||||
|
toolType.value = type;
|
||||||
|
|
||||||
|
// 通过 ToolManager 切换工具,这会自动通知 partManager
|
||||||
|
if (props.toolManager) {
|
||||||
|
props.toolManager.setToolWithCommand(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 备用方案:如果没有 toolManager,直接更新 partManager
|
||||||
|
// else if (props.partManager) {
|
||||||
|
// props.partManager.setPartType(type);
|
||||||
|
// props.partManager.setupPartEvents();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
function onCreate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
// 复制并创建
|
||||||
|
function onCopyCreate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.part-selector-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 22px;
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
max-width: min(90vw, 700px);
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
user-select: none;
|
||||||
|
&.active {
|
||||||
|
transform: translateY(100%);
|
||||||
|
> .btn {
|
||||||
|
> i {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 22px;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板和手机适配 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.part-selector-toolbar {
|
||||||
|
bottom: 15px;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
max-width: calc(100vw - 30px);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.part-selector-toolbar {
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.part-selector-toolbar.is-active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-header {
|
||||||
|
// display: flex;
|
||||||
|
// justify-content: center;
|
||||||
|
// align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
// border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
position: relative;
|
||||||
|
> .tip {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
> img {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
> span {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.header-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #333;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
min-width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-section {
|
||||||
|
padding: 0 3rem 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-types {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn span {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn.active {
|
||||||
|
background-color: #007aff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 5px;
|
||||||
|
padding: 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板适配 - 每行4个按钮 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.tool-actions {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px 6px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机适配 - 每行3个按钮 */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.tool-actions {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 6px 4px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-btn {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
min-width: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
// flex-direction: column;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
gap: 4px;
|
||||||
|
.c-svg {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
color: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,744 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="fade">
|
|
||||||
<div
|
|
||||||
class="select-menu-panel"
|
|
||||||
v-if="visible"
|
|
||||||
:class="{ active: !closePanel }"
|
|
||||||
>
|
|
||||||
<div class="btn" @click="setClosePanel">
|
|
||||||
<i class="fi fi-br-angle-left"></i>
|
|
||||||
</div>
|
|
||||||
<!-- 变换工具顶部 -->
|
|
||||||
<div class="panel-select">
|
|
||||||
<!-- <div class="panel-header">
|
|
||||||
<div class="header-title">变换工具</div>
|
|
||||||
</div> -->
|
|
||||||
<!-- 分割线 -->
|
|
||||||
<!-- <div class="panel-divider"></div> -->
|
|
||||||
<!-- 变换工具内容 -->
|
|
||||||
<div class="tool-content">
|
|
||||||
<div
|
|
||||||
class="object-item"
|
|
||||||
v-for="v in activeObjects"
|
|
||||||
:key="v.id"
|
|
||||||
>
|
|
||||||
<div class="title">{{ v.layer?.name }}</div>
|
|
||||||
<div class="list">
|
|
||||||
<div>
|
|
||||||
<span class="label">W</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
:value="v.width"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="label">H</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
:value="v.height"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- <div>
|
|
||||||
<span class="label">X</span>
|
|
||||||
<input type="number" :value="v.left" disabled />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="label">Y</span>
|
|
||||||
<input type="number" :value="v.top" disabled />
|
|
||||||
</div> -->
|
|
||||||
<div>
|
|
||||||
<span class="label iconfont icon-angle"></span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
:value="Number(Number(v.angle).toFixed(3))"
|
|
||||||
@change="(e) => changeAngle(e, v)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="btn" @click="clickflipHorizontal(v)">
|
|
||||||
<i class="iconfont icon-flip-horizontal"></i>
|
|
||||||
<p class="tip">
|
|
||||||
{{ t("Canvas.flipHorizontal") }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="btn" @click="clickflipVertical(v)">
|
|
||||||
<i class="iconfont icon-flip-vertical"></i>
|
|
||||||
<p class="tip">
|
|
||||||
{{ t("Canvas.flipVertical") }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="btn" @click="clickCropImage(v)">
|
|
||||||
<i class="iconfont icon-caijian"></i>
|
|
||||||
<p class="tip">
|
|
||||||
{{ t("Canvas.cropAndAdd") }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import showViewVideo from "@/tool/mount";
|
|
||||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
|
||||||
import { useI18n } from "vue-i18n";
|
|
||||||
import { ToolCommand } from "../commands/ToolCommands";
|
|
||||||
import { OperationType } from "../utils/layerHelper";
|
|
||||||
import { loadImageUrlToLayer } from "../utils/imageHelper";
|
|
||||||
import { TransformCommand } from "../commands/StateCommands";
|
|
||||||
const props = defineProps({
|
|
||||||
canvas: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
commandManager: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
selectManager: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
layerManager: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
toolManager: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
activeTool: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const visible = ref(false);
|
|
||||||
//打开隐藏操作面板
|
|
||||||
const closePanel = ref(false);
|
|
||||||
const setClosePanel = () => {
|
|
||||||
closePanel.value = !closePanel.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 国际化
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setupCanvasListeners();
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
removeCanvasListeners();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听 activeTool 变化
|
|
||||||
watch(
|
|
||||||
() => props.activeTool,
|
|
||||||
(newTool) => {
|
|
||||||
if (newTool === OperationType.SELECT) {
|
|
||||||
show();
|
|
||||||
} else {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示面板
|
|
||||||
*/
|
|
||||||
function show() {
|
|
||||||
if (activeObjects.value.length === 0) return;
|
|
||||||
visible.value = true;
|
|
||||||
closePanel.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭面板
|
|
||||||
*/
|
|
||||||
function close() {
|
|
||||||
visible.value = false;
|
|
||||||
}
|
|
||||||
// 获取当前选中的对象
|
|
||||||
const activeObjects = ref([]);
|
|
||||||
const getActiveObject = (e) => {
|
|
||||||
console.log("==========切换激活对象", e);
|
|
||||||
activeObjects.value = e.selected.map((v) => v);
|
|
||||||
activeObjects.value.forEach((v) => {
|
|
||||||
v.layer = props.layerManager.getLayerById(v.layerId);
|
|
||||||
});
|
|
||||||
if (activeObjects.value.length === 0) {
|
|
||||||
close();
|
|
||||||
} else {
|
|
||||||
show(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const lastSelectLayerId = inject("lastSelectLayerId");
|
|
||||||
const layers = inject("layers");
|
|
||||||
const transformObject = (activeObj, initialState, finalState) => {
|
|
||||||
const transformCmd = new TransformCommand({
|
|
||||||
canvas: props.canvas,
|
|
||||||
objectId: activeObj.id,
|
|
||||||
initialState,
|
|
||||||
finalState,
|
|
||||||
objectType: activeObj.type,
|
|
||||||
name: `变换 ${activeObj.type || "对象"}`,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
layers: layers,
|
|
||||||
lastSelectLayerId: lastSelectLayerId,
|
|
||||||
});
|
|
||||||
props.layerManager.commandManager.execute(transformCmd, {
|
|
||||||
name: "对象修改",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据左上角坐标计算旋转后的新坐标
|
|
||||||
* @param {number} W - 宽度
|
|
||||||
* @param {number} H - 高度
|
|
||||||
* @param {number} currentX - 当前左上角x坐标
|
|
||||||
* @param {number} currentY - 当前左上角y坐标
|
|
||||||
* @param {number} currentAngleDeg - 当前角度(度)
|
|
||||||
* @param {number} newAngleDeg - 新角度(度)
|
|
||||||
* @returns {Object} 旋转后的左上角坐标 {x, y}
|
|
||||||
*/
|
|
||||||
function calculateRotatedTopLeftDeg(
|
|
||||||
W,
|
|
||||||
H,
|
|
||||||
currentX,
|
|
||||||
currentY,
|
|
||||||
currentAngleDeg,
|
|
||||||
newAngleDeg
|
|
||||||
) {
|
|
||||||
const currentAngle = (currentAngleDeg * Math.PI) / 180;
|
|
||||||
const newAngle = (newAngleDeg * Math.PI) / 180;
|
|
||||||
// 1. 用当前角度计算中心点位置
|
|
||||||
const cosCurrent = Math.cos(currentAngle);
|
|
||||||
const sinCurrent = Math.sin(currentAngle);
|
|
||||||
const Cx = currentX + (W / 2) * cosCurrent - (H / 2) * sinCurrent;
|
|
||||||
const Cy = currentY + (W / 2) * sinCurrent + (H / 2) * cosCurrent;
|
|
||||||
|
|
||||||
// 2. 用新角度计算旋转后的左上角位置
|
|
||||||
const cosNew = Math.cos(newAngle);
|
|
||||||
const sinNew = Math.sin(newAngle);
|
|
||||||
const newX = Cx + (-W / 2) * cosNew - (-H / 2) * sinNew;
|
|
||||||
const newY = Cy + (-W / 2) * sinNew + (-H / 2) * cosNew;
|
|
||||||
|
|
||||||
return { x: newX, y: newY };
|
|
||||||
}
|
|
||||||
// 改变角度
|
|
||||||
const changeAngle = (e, obj) => {
|
|
||||||
const initialState = TransformCommand.captureTransformState(obj);
|
|
||||||
const finalState = { ...initialState };
|
|
||||||
const angle = e.target.value;
|
|
||||||
if (obj.originX === "left" && obj.originY === "top") {
|
|
||||||
const width = obj.width * obj.scaleX;
|
|
||||||
const height = obj.height * obj.scaleY;
|
|
||||||
const left = obj.left;
|
|
||||||
const top = obj.top;
|
|
||||||
const { x, y } = calculateRotatedTopLeftDeg(
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
obj.angle,
|
|
||||||
angle
|
|
||||||
);
|
|
||||||
finalState.left = x;
|
|
||||||
finalState.top = y;
|
|
||||||
}
|
|
||||||
finalState.angle = angle;
|
|
||||||
transformObject(obj, initialState, finalState);
|
|
||||||
};
|
|
||||||
// 水平翻转
|
|
||||||
const clickflipHorizontal = (obj) => {
|
|
||||||
const initialState = TransformCommand.captureTransformState(obj);
|
|
||||||
const finalState = { ...initialState };
|
|
||||||
finalState.flipX = !finalState.flipX;
|
|
||||||
transformObject(obj, initialState, finalState);
|
|
||||||
};
|
|
||||||
// 垂直翻转
|
|
||||||
const clickflipVertical = (obj) => {
|
|
||||||
const initialState = TransformCommand.captureTransformState(obj);
|
|
||||||
const finalState = { ...initialState };
|
|
||||||
finalState.flipY = !finalState.flipY;
|
|
||||||
transformObject(obj, initialState, finalState);
|
|
||||||
};
|
|
||||||
// 裁剪图片
|
|
||||||
const cropImage = inject("cropImage");
|
|
||||||
const clickCropImage = async (obj) => {
|
|
||||||
const base64 = await props.layerManager.getLayerToBase64(obj.layerId);
|
|
||||||
if(base64) cropImage(base64).then((res) => {
|
|
||||||
loadImageUrlToLayer({
|
|
||||||
imageUrl: res,
|
|
||||||
layerManager: props.layerManager,
|
|
||||||
canvas: props.canvas,
|
|
||||||
toolManager: props.toolManager,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateActiveObjects = (arrs, keys) => {
|
|
||||||
arrs.forEach((v) => {
|
|
||||||
activeObjects.value.forEach((item) => {
|
|
||||||
if (item.id === v.id) {
|
|
||||||
keys.forEach((key) => (item[key] = v[key]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
activeObjects.value = [...activeObjects.value];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const objectRotatingChange = (e) => {
|
|
||||||
const arrs = [];
|
|
||||||
if (e.target._objects) {
|
|
||||||
e.target._objects.forEach((v) => arrs.push(v));
|
|
||||||
} else {
|
|
||||||
arrs.push(e.target);
|
|
||||||
}
|
|
||||||
updateActiveObjects(arrs, ["angle"]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置画布事件监听
|
|
||||||
*/
|
|
||||||
function setupCanvasListeners() {
|
|
||||||
if (!props.canvas) return;
|
|
||||||
// 鼠标事件
|
|
||||||
props.canvas.on("selection:created", getActiveObject);
|
|
||||||
props.canvas.on("selection:updated", getActiveObject);
|
|
||||||
props.canvas.on("selection:cleared", close);
|
|
||||||
props.canvas.on("object:rotating", objectRotatingChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除画布事件监听
|
|
||||||
*/
|
|
||||||
function removeCanvasListeners() {
|
|
||||||
if (!props.canvas) return;
|
|
||||||
|
|
||||||
// 移除鼠标事件
|
|
||||||
props.canvas.off("selection:created", getActiveObject);
|
|
||||||
props.canvas.off("selection:updated", getActiveObject);
|
|
||||||
props.canvas.off("selection:cleared", close);
|
|
||||||
props.canvas.off("object:rotating", objectRotatingChange);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.select-menu-panel {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 22px;
|
|
||||||
left: 20px;
|
|
||||||
right: 20px;
|
|
||||||
max-width: min(90vw, 640px);
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(15px);
|
|
||||||
-webkit-backdrop-filter: blur(15px);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
color: #333;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
user-select: none;
|
|
||||||
&.active {
|
|
||||||
transform: translateY(100%);
|
|
||||||
> .btn {
|
|
||||||
> i {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 22px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
transform: rotate(270deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板和手机适配 */
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.select-menu-panel {
|
|
||||||
bottom: 15px;
|
|
||||||
left: 15px;
|
|
||||||
right: 15px;
|
|
||||||
max-width: calc(100vw - 30px);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
.select-menu-panel {
|
|
||||||
bottom: 10px;
|
|
||||||
left: 10px;
|
|
||||||
right: 10px;
|
|
||||||
max-width: calc(100vw - 20px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-menu-panel.is-active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-header {
|
|
||||||
padding: 8px 15px;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-select {
|
|
||||||
// padding: 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板适配 */
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.panel-header {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机适配 */
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
.panel-header {
|
|
||||||
padding: 5px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-btn {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px;
|
|
||||||
color: #333;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-btn span {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-btn svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-btn:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-btn.active {
|
|
||||||
background-color: #007aff;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-divider {
|
|
||||||
height: 1px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
margin: 0 10px 5px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-content {
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 220px;
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 0 10px;
|
|
||||||
> .object-item {
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
padding: 10px 0;
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
> .title {
|
|
||||||
text-align: left;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
> .list {
|
|
||||||
display: flex;
|
|
||||||
> div {
|
|
||||||
margin-right: 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #474747;
|
|
||||||
> .label {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
> input {
|
|
||||||
width: 65px;
|
|
||||||
}
|
|
||||||
.iconfont {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> div.btn {
|
|
||||||
position: relative;
|
|
||||||
min-width: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
> .tip {
|
|
||||||
position: absolute;
|
|
||||||
top: -5px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -100%);
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
color: white;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
margin-left: 0.8rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
pointer-events: none;
|
|
||||||
display: none;
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 97%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 5px solid transparent;
|
|
||||||
border-right: 5px solid transparent;
|
|
||||||
border-top: 5px solid rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.08);
|
|
||||||
> .tip {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平板适配 - 每行4个按钮 */
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.tool-content {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 8px 6px;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机适配 - 每行3个按钮 */
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
.tool-content {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 6px 4px;
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-btn {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
min-width: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
display: flex;
|
|
||||||
// flex-direction: column;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #333;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
gap: 4px;
|
|
||||||
.c-svg {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn svg {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:hover {
|
|
||||||
color: #007aff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 对话框样式 */
|
|
||||||
.dialog-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
-webkit-backdrop-filter: blur(5px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-container {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 12px;
|
|
||||||
width: 280px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 15px;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-dialog-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #666;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-content {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feather-control {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-control {
|
|
||||||
flex: 1;
|
|
||||||
height: 4px;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 2px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-control::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #007aff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feather-value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn,
|
|
||||||
.confirm-btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background-color: #007aff;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
|
||||||
}
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<div class="repeat-setting">
|
||||||
|
<div class="title">{{ t("Canvas.repeatSetting") }}</div>
|
||||||
|
<div class="repeat-setting-item">
|
||||||
|
<span class="label">{{ t("Canvas.angle") }}</span>
|
||||||
|
<angle-tool
|
||||||
|
:angle="angle"
|
||||||
|
@input="(e) => emit('inputFillAngle', e)"
|
||||||
|
@change="(e) => emit('changeFillAngle', e)"
|
||||||
|
style-type="2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="repeat-setting-item">
|
||||||
|
<span class="label">{{ t("Canvas.scale") }}</span>
|
||||||
|
<slider
|
||||||
|
:min="1"
|
||||||
|
:max="500"
|
||||||
|
:step="1"
|
||||||
|
is-input
|
||||||
|
:tipFormatter="(v) => `${scale}%`"
|
||||||
|
:value="scale"
|
||||||
|
@input="inputFillScale"
|
||||||
|
@change="changeFillScale"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="repeat-setting-item">
|
||||||
|
<span class="label">Gap X</span>
|
||||||
|
<slider
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
is-input
|
||||||
|
:tipFormatter="(v) => `${v}px`"
|
||||||
|
:value="gapX"
|
||||||
|
@input="(e) => emit('inputFill_Gap', e, gapY)"
|
||||||
|
@change="(e) => emit('changeFill_Gap', e, gapY)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="repeat-setting-item">
|
||||||
|
<span class="label">Gap Y</span>
|
||||||
|
<slider
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:step="1"
|
||||||
|
is-input
|
||||||
|
:tipFormatter="(v) => `${v}px`"
|
||||||
|
:value="gapY"
|
||||||
|
@input="(e) => emit('inputFill_Gap', gapX, e)"
|
||||||
|
@change="(e) => emit('changeFill_Gap', gapX, e)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="repeat-setting-item">
|
||||||
|
<span class="label">{{ t("Canvas.offset") }}</span>
|
||||||
|
<offset-tool
|
||||||
|
:left="offsetX"
|
||||||
|
:top="offsetY"
|
||||||
|
@input="(e) => emit('inputFillOffset', e)"
|
||||||
|
@change="(e) => emit('changeFillOffset', e)"
|
||||||
|
:show-dish="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="repeat-setting-item offset">
|
||||||
|
<offset-tool
|
||||||
|
:left="offsetX"
|
||||||
|
:top="offsetY"
|
||||||
|
@input="(e) => emit('inputFillOffset', e)"
|
||||||
|
@change="(e) => emit('changeFillOffset', e)"
|
||||||
|
:show-input="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, computed } from "vue";
|
||||||
|
import { getTransformScaleAngle } from "../../utils/helper";
|
||||||
|
import AngleTool from "../tools/AngleTool.vue";
|
||||||
|
import OffsetTool from "../tools/OffsetTool.vue";
|
||||||
|
import Slider from "../tools/Slider.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
object: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const angle = computed(
|
||||||
|
() => getTransformScaleAngle(props.object.fill?.patternTransform).angle
|
||||||
|
);
|
||||||
|
const scale = computed(() => {
|
||||||
|
const patternTransform = props.object.fill?.patternTransform;
|
||||||
|
const scaleValue = getTransformScaleAngle(patternTransform).scale * 100;
|
||||||
|
return Number(Number(scaleValue).toFixed(2));
|
||||||
|
});
|
||||||
|
const gapX = computed(() => props.object.fill_?.gapX || 0);
|
||||||
|
const gapY = computed(() => props.object.fill_?.gapY || 0);
|
||||||
|
const offsetX = computed(
|
||||||
|
() => (props.object.fill?.offsetX / props.object.width) * 100
|
||||||
|
);
|
||||||
|
const offsetY = computed(
|
||||||
|
() => (props.object.fill?.offsetY / props.object.height) * 100
|
||||||
|
);
|
||||||
|
const emit = defineEmits([
|
||||||
|
"inputFillAngle",
|
||||||
|
"changeFillAngle",
|
||||||
|
"inputFillOffset",
|
||||||
|
"changeFillOffset",
|
||||||
|
"inputFillScale",
|
||||||
|
"changeFillScale",
|
||||||
|
"inputFill_Gap",
|
||||||
|
"changeFill_Gap",
|
||||||
|
]);
|
||||||
|
const inputFillScale = (e) => {
|
||||||
|
const scale = e / 100;
|
||||||
|
emit("inputFillScale", scale);
|
||||||
|
};
|
||||||
|
const changeFillScale = (e) => {
|
||||||
|
const scale = e / 100;
|
||||||
|
emit("changeFillScale", scale);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.repeat-setting {
|
||||||
|
user-select: none;
|
||||||
|
width: 228px;
|
||||||
|
> .title {
|
||||||
|
line-height: 35px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
> .repeat-setting-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&.offset {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
> .label {
|
||||||
|
min-width: 68px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
&:not(.offset) > div {
|
||||||
|
width: 120px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
> .slider {
|
||||||
|
--slider-thumb-color1: #000;
|
||||||
|
--slider-thumb-color2: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
import i18n from "@/lang/index.ts";
|
||||||
|
const { t } = i18n.global;
|
||||||
|
|
||||||
|
/** 填充重复模式 */
|
||||||
|
export const getSelectOptions = () => ref([
|
||||||
|
{ value: "no-repeat", label: t("Canvas.noRepeat") },
|
||||||
|
{ value: "repeat", label: t("Canvas.repeat") },
|
||||||
|
{ value: "repeat-x", label: t("Canvas.repeatX") },
|
||||||
|
{ value: "repeat-y", label: t("Canvas.repeatY") },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** 图层混合模式 */
|
||||||
|
export const getLayerCompositeOptions = () => ref([
|
||||||
|
{ value: "source-over", label: t("Canvas.CompositeNormal"), tip: t("Canvas.CompositeNormalTip") },// 正常
|
||||||
|
{ value: "darken", label: t("Canvas.CompositeDarken"), tip: t("Canvas.CompositeDarkenTip") },// 变暗
|
||||||
|
{ value: "multiply", label: t("Canvas.CompositeMultiply"), tip: t("Canvas.CompositeMultiplyTip") },// 正片叠底
|
||||||
|
{ value: "color-burn", label: t("Canvas.CompositeColorBurn"), tip: t("Canvas.CompositeColorBurnTip") },// 颜色加深
|
||||||
|
|
||||||
|
{ value: "lighten", label: t("Canvas.CompositeLighten"), tip: t("Canvas.CompositeLightenTip") },// 颜色减淡
|
||||||
|
{ value: "screen", label: t("Canvas.CompositeScreen"), tip: t("Canvas.CompositeScreenTip") },// 滤色
|
||||||
|
{ value: "color-dodge", label: t("Canvas.CompositeColorDodge"), tip: t("Canvas.CompositeColorDodgeTip") },// 颜色减淡
|
||||||
|
{ value: "lighter", label: t("Canvas.CompositeLighter"), tip: t("Canvas.CompositeLighterTip") },// 颜色减淡
|
||||||
|
|
||||||
|
{ value: "overlay", label: t("Canvas.CompositeOverlay"), tip: t("Canvas.CompositeOverlayTip") },// 叠加
|
||||||
|
{ value: "soft-light", label: t("Canvas.CompositeSoftLight"), tip: t("Canvas.CompositeSoftLightTip") },// 柔光
|
||||||
|
{ value: "hard-light", label: t("Canvas.CompositeHardLight"), tip: t("Canvas.CompositeHardLightTip") },// 强光
|
||||||
|
|
||||||
|
{ value: "difference", label: t("Canvas.CompositeDifference"), tip: t("Canvas.CompositeDifferenceTip") },// 差值
|
||||||
|
{ value: "exclusion", label: t("Canvas.CompositeExclusion"), tip: t("Canvas.CompositeExclusionTip") },// 排除
|
||||||
|
|
||||||
|
{ value: "hue", label: t("Canvas.CompositeHue"), tip: t("Canvas.CompositeHueTip") },// 色相
|
||||||
|
{ value: "saturation", label: t("Canvas.CompositeSaturation"), tip: t("Canvas.CompositeSaturationTip") },// 饱和度
|
||||||
|
{ value: "color", label: t("Canvas.CompositeColor"), tip: t("Canvas.CompositeColorTip") },// 颜色
|
||||||
|
{ value: "luminosity", label: t("Canvas.CompositeLuminosity"), tip: t("Canvas.CompositeLuminosityTip") },// 亮度
|
||||||
|
|
||||||
|
// { value: "destination-over", label: "背后", tip:"背后:新图形绘制到原内容下方" },
|
||||||
|
// { value: "source-in", label: "颜色加深", tip:"颜色加深:只显示重叠部分,其他透明" },
|
||||||
|
// { value: "destination-in", label: "颜色减淡", tip:"颜色减淡:只显示原内容与新图形重叠部分" },
|
||||||
|
// { value: "source-out", label: "排除", tip:"排除:只显示新图形中不重叠部分" },
|
||||||
|
// { value: "destination-out", label: "差值", tip:"差值:只清除原内容中与新图形重叠部分" },
|
||||||
|
// { value: "xor", label: "排除", tip:"排除:重叠部分透明" },
|
||||||
|
// { value: "copy", label: "正常", tip:"正常:完全忽略原内容,只显示新图形" },
|
||||||
|
// { value: "source-atop", label: "叠加", tip:"叠加:只在与现有内容重叠处绘制新图形" },
|
||||||
|
// { value: "destination-atop", label: "柔光", tip:"柔光:仅保留重叠部分,新图形在原内容后绘制" },
|
||||||
|
// { value: "darker", label: "变暗", tip:"变暗:重叠部分颜色减淡" },
|
||||||
|
]);
|
||||||
@@ -0,0 +1,900 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div
|
||||||
|
class="select-menu-panel"
|
||||||
|
v-if="visible"
|
||||||
|
:class="{ active: !closePanel }"
|
||||||
|
>
|
||||||
|
<div class="btn" @click="setClosePanel">
|
||||||
|
<i class="fi fi-br-angle-left"></i>
|
||||||
|
</div>
|
||||||
|
<!-- 变换工具顶部 -->
|
||||||
|
<div class="panel-select">
|
||||||
|
<!-- <div class="panel-header">
|
||||||
|
<div class="header-title">变换工具</div>
|
||||||
|
</div> -->
|
||||||
|
<!-- 分割线 -->
|
||||||
|
<!-- <div class="panel-divider"></div> -->
|
||||||
|
<!-- 变换工具内容 -->
|
||||||
|
<div class="tool-content">
|
||||||
|
<div
|
||||||
|
class="object-item"
|
||||||
|
v-for="v in activeObjects"
|
||||||
|
:key="v.id"
|
||||||
|
>
|
||||||
|
<div class="title">{{ v.layer?.name }}</div>
|
||||||
|
<div class="list">
|
||||||
|
<div
|
||||||
|
class="input"
|
||||||
|
v-if="v.layerId !== SpecialLayerId.COLOR"
|
||||||
|
>
|
||||||
|
<angle-tool
|
||||||
|
:angle="Number(Number(v.angle).toFixed(3))"
|
||||||
|
@input="(e) => inputAngle(e, v)"
|
||||||
|
@change="(e) => changeAngle(e, v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<span class="label"
|
||||||
|
>{{ t("Canvas.opacity") }}:</span
|
||||||
|
>
|
||||||
|
<slider
|
||||||
|
:tipFormatter="
|
||||||
|
(v) => `${Math.round(v * 100)}%`
|
||||||
|
"
|
||||||
|
:value="v.opacity"
|
||||||
|
:min="0"
|
||||||
|
:max="1"
|
||||||
|
:step="0.01"
|
||||||
|
@change="(e) => changeOpacity(e, v)"
|
||||||
|
@input="(e) => inputOpacity(e, v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="btn"
|
||||||
|
@click="clickflipHorizontal(v)"
|
||||||
|
v-if="v.layerId !== SpecialLayerId.COLOR"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-flip-horizontal"></i>
|
||||||
|
<p class="tip">
|
||||||
|
{{ t("Canvas.flipHorizontal") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="btn"
|
||||||
|
@click="clickflipVertical(v)"
|
||||||
|
v-if="v.layerId !== SpecialLayerId.COLOR"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-flip-vertical"></i>
|
||||||
|
<p class="tip">
|
||||||
|
{{ t("Canvas.flipVertical") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div
|
||||||
|
class="btn"
|
||||||
|
@click="clickCropImage(v)"
|
||||||
|
v-if="v.layerId !== SpecialLayerId.COLOR"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-caijian"></i>
|
||||||
|
<p class="tip">
|
||||||
|
{{ t("Canvas.cropAndAdd") }}
|
||||||
|
</p>
|
||||||
|
</div> -->
|
||||||
|
<!-- <div
|
||||||
|
class="btn"
|
||||||
|
@click="clickRasterizeLayer(v)"
|
||||||
|
v-if="v.type !== 'image'"
|
||||||
|
>
|
||||||
|
<span class="label">{{ t("Canvas.RasterizedLayer") }}</span>
|
||||||
|
</div> -->
|
||||||
|
<div class="select">
|
||||||
|
<!-- 混合模式 -->
|
||||||
|
<i class="iconfont icon-hunhemoshi"></i>
|
||||||
|
<my-select
|
||||||
|
:defaultValue="
|
||||||
|
v.layer?.blendMode ||
|
||||||
|
v.globalCompositeOperation
|
||||||
|
"
|
||||||
|
:list="layerCompositeOptions"
|
||||||
|
@change="
|
||||||
|
(n, o) => setLayerComposite(n, o, v, 1)
|
||||||
|
"
|
||||||
|
@active="
|
||||||
|
(n, o) => setLayerComposite(n, o, v, 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- <div
|
||||||
|
class="btn"
|
||||||
|
@click="clickTest(v)"
|
||||||
|
>
|
||||||
|
<span class="label">测试</span>
|
||||||
|
</div> -->
|
||||||
|
<div
|
||||||
|
class="select"
|
||||||
|
v-if="v.type === 'rect' || v.type === 'image'"
|
||||||
|
>
|
||||||
|
<!-- 平铺 -->
|
||||||
|
<i class="iconfont icon-repeat"></i>
|
||||||
|
<a-select
|
||||||
|
size="small"
|
||||||
|
:defaultValue="
|
||||||
|
typeof v.fill === 'object'
|
||||||
|
? v.fill?.repeat || 'no-repeat'
|
||||||
|
: 'no-repeat'
|
||||||
|
"
|
||||||
|
:options="selectOptions"
|
||||||
|
@change="(e) => changeFillRepeat(e, v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 平铺设置 -->
|
||||||
|
<a-popover
|
||||||
|
v-if="v.type === 'rect'"
|
||||||
|
trigger="click"
|
||||||
|
destroyTooltipOnHide
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<repeat-setting
|
||||||
|
:object="v"
|
||||||
|
@inputFillAngle="
|
||||||
|
(e) => inputFillAngle(e, v)
|
||||||
|
"
|
||||||
|
@changeFillAngle="
|
||||||
|
(e) => changeFillAngle(e, v)
|
||||||
|
"
|
||||||
|
@inputFillOffset="
|
||||||
|
(e) => inputFillOffset(e, v)
|
||||||
|
"
|
||||||
|
@changeFillOffset="
|
||||||
|
(e) => changeFillOffset(e, v)
|
||||||
|
"
|
||||||
|
@inputFillScale="
|
||||||
|
(e) => inputFillScale(e, v)
|
||||||
|
"
|
||||||
|
@changeFillScale="
|
||||||
|
(e) => changeFillScale(e, v)
|
||||||
|
"
|
||||||
|
@inputFill_Gap="
|
||||||
|
(x, y) => inputFill_Gap(x, y, v)
|
||||||
|
"
|
||||||
|
@changeFill_Gap="
|
||||||
|
(x, y) => changeFill_Gap(x, y, v)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="btn">
|
||||||
|
<SvgIcon name="overallMore" size="18" />
|
||||||
|
</div>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, onUnmounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
import { OperationType, SpecialLayerId } from "../../utils/layerHelper";
|
||||||
|
import { loadImageUrlToLayer } from "../../utils/imageHelper";
|
||||||
|
import {
|
||||||
|
calculateRotatedTopLeftDeg,
|
||||||
|
createPatternTransform,
|
||||||
|
getTransformScaleAngle,
|
||||||
|
} from "../../utils/helper";
|
||||||
|
import { TransformCommand } from "../../commands/StateCommands";
|
||||||
|
import {
|
||||||
|
FillRepeatCommand,
|
||||||
|
FillRepeatChangeCommand,
|
||||||
|
FillRepeatGapChangeCommand,
|
||||||
|
} from "../../commands/FillRepeatCommand";
|
||||||
|
import { SetLayerCompositeCommand } from "../../commands/LayerCommands.js";
|
||||||
|
import RepeatSetting from "./RepeatSetting.vue";
|
||||||
|
import Slider from "../tools/Slider.vue";
|
||||||
|
import AngleTool from "../tools/AngleTool.vue";
|
||||||
|
import MySelect from "../tools/MySelect.vue";
|
||||||
|
import EventManager from "../../utils/event.js";
|
||||||
|
import { getSelectOptions, getLayerCompositeOptions } from "./data.js";
|
||||||
|
const selectOptions = getSelectOptions();
|
||||||
|
const layerCompositeOptions = getLayerCompositeOptions();
|
||||||
|
const props = defineProps({
|
||||||
|
canvas: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
commandManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
layerManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canvasManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
toolManager: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
activeTool: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 响应式数据
|
||||||
|
const visible = ref(false);
|
||||||
|
//打开隐藏操作面板
|
||||||
|
const closePanel = ref(false);
|
||||||
|
const setClosePanel = () => {
|
||||||
|
closePanel.value = !closePanel.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setupCanvasListeners();
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
removeCanvasListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 activeTool 变化
|
||||||
|
watch(
|
||||||
|
() => props.activeTool,
|
||||||
|
(newTool) => {
|
||||||
|
if (newTool === OperationType.SELECT) {
|
||||||
|
show();
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示面板
|
||||||
|
*/
|
||||||
|
function show() {
|
||||||
|
if (activeObjects.value.length === 0) return;
|
||||||
|
visible.value = true;
|
||||||
|
closePanel.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭面板
|
||||||
|
*/
|
||||||
|
function close() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
// 获取当前选中的对象
|
||||||
|
const activeObjects = ref([]);
|
||||||
|
const getActiveObject = (e) => {
|
||||||
|
console.log("==========切换激活对象", e, activeObjects);
|
||||||
|
activeObjects.value = [...e.selected];
|
||||||
|
activeObjects.value.forEach((v) => {
|
||||||
|
v.layer = props.layerManager.getLayerById(v.layerId);
|
||||||
|
});
|
||||||
|
if (activeObjects.value.length === 0) {
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//取消当前选中
|
||||||
|
const cancelSelect = () => {
|
||||||
|
activeObjects.value = [];
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
const lastSelectLayerId = inject("lastSelectLayerId");
|
||||||
|
const layers = inject("layers");
|
||||||
|
const transformObject = (
|
||||||
|
activeObj,
|
||||||
|
initialState,
|
||||||
|
finalState,
|
||||||
|
isCommand = true
|
||||||
|
) => {
|
||||||
|
const cmd = new TransformCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
objectId: activeObj.id,
|
||||||
|
initialState,
|
||||||
|
finalState,
|
||||||
|
objectType: activeObj.type,
|
||||||
|
name: `变换 ${activeObj.type || "对象"}`,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layers: layers,
|
||||||
|
lastSelectLayerId: lastSelectLayerId,
|
||||||
|
});
|
||||||
|
if (isCommand) {
|
||||||
|
props.commandManager.execute(cmd);
|
||||||
|
} else {
|
||||||
|
cmd.execute();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 改变不透明度
|
||||||
|
const changeOpacity = (opacity, obj) => {
|
||||||
|
props.layerManager?.setLayerOpacity(obj.layerId, opacity);
|
||||||
|
};
|
||||||
|
const inputOpacity = (opacity, obj) => {
|
||||||
|
obj.opacity = opacity;
|
||||||
|
props.canvas.renderAll();
|
||||||
|
};
|
||||||
|
// 改变角度
|
||||||
|
const inputAngle = (angle, obj) => {
|
||||||
|
const initialState = TransformCommand.captureTransformState(obj);
|
||||||
|
const finalState = computeAngleState(angle, obj, initialState);
|
||||||
|
transformObject(obj, initialState, finalState, false);
|
||||||
|
if (!obj.hasOwnProperty("oldState")) obj.oldState = initialState;
|
||||||
|
};
|
||||||
|
const changeAngle = (angle, obj) => {
|
||||||
|
var initialState;
|
||||||
|
if (obj.hasOwnProperty("oldState")) {
|
||||||
|
initialState = obj.oldState;
|
||||||
|
delete obj.oldState;
|
||||||
|
} else {
|
||||||
|
initialState = TransformCommand.captureTransformState(obj);
|
||||||
|
}
|
||||||
|
const finalState = computeAngleState(angle, obj, initialState);
|
||||||
|
transformObject(obj, initialState, finalState);
|
||||||
|
};
|
||||||
|
const computeAngleState = (angle, obj, initialState) => {
|
||||||
|
const finalState = { ...initialState };
|
||||||
|
if (obj.originX === "left" && obj.originY === "top") {
|
||||||
|
const width = obj.width * obj.scaleX;
|
||||||
|
const height = obj.height * obj.scaleY;
|
||||||
|
const left = obj.left;
|
||||||
|
const top = obj.top;
|
||||||
|
const { x, y } = calculateRotatedTopLeftDeg(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
obj.angle,
|
||||||
|
angle
|
||||||
|
);
|
||||||
|
finalState.left = x;
|
||||||
|
finalState.top = y;
|
||||||
|
}
|
||||||
|
finalState.angle = angle;
|
||||||
|
return finalState;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 水平翻转
|
||||||
|
const clickflipHorizontal = (obj) => {
|
||||||
|
const initialState = TransformCommand.captureTransformState(obj);
|
||||||
|
const finalState = { ...initialState };
|
||||||
|
finalState.flipX = !finalState.flipX;
|
||||||
|
transformObject(obj, initialState, finalState);
|
||||||
|
};
|
||||||
|
// 垂直翻转
|
||||||
|
const clickflipVertical = (obj) => {
|
||||||
|
const initialState = TransformCommand.captureTransformState(obj);
|
||||||
|
const finalState = { ...initialState };
|
||||||
|
finalState.flipY = !finalState.flipY;
|
||||||
|
transformObject(obj, initialState, finalState);
|
||||||
|
};
|
||||||
|
// 裁剪图片
|
||||||
|
const cropImage = inject("cropImage");
|
||||||
|
const clickCropImage = async (obj) => {
|
||||||
|
const base64 = await props.layerManager.getLayerToBase64(obj.layerId);
|
||||||
|
if (base64)
|
||||||
|
cropImage(base64).then((res) => {
|
||||||
|
loadImageUrlToLayer({
|
||||||
|
imageUrl: res,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
canvas: props.canvas,
|
||||||
|
toolManager: props.toolManager,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 栅格化图层
|
||||||
|
const clickRasterizeLayer = (obj) => {
|
||||||
|
props.layerManager.rasterizeLayer(obj.layerId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 改变填充重复
|
||||||
|
const changeFillRepeat = async (value, obj) => {
|
||||||
|
console.log("==========改变填充重复", obj.type);
|
||||||
|
const cmd = new FillRepeatCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
layers: layers,
|
||||||
|
canvasManager: props.canvasManager,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layerId: obj.layerId,
|
||||||
|
fillRepeat: value,
|
||||||
|
});
|
||||||
|
props.commandManager.execute(cmd);
|
||||||
|
};
|
||||||
|
// 改变填充角度
|
||||||
|
const inputFillAngle = (angle, obj) => {
|
||||||
|
if (!obj.oldPattern) obj.oldPattern = obj.get("fill");
|
||||||
|
const fill = obj.get("fill");
|
||||||
|
const scale = getTransformScaleAngle(fill?.patternTransform).scale;
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
...fill,
|
||||||
|
patternTransform: createPatternTransform(scale, angle),
|
||||||
|
});
|
||||||
|
obj.set("fill", pattern);
|
||||||
|
props.canvas.renderAll();
|
||||||
|
};
|
||||||
|
const changeFillAngle = (angle, obj) => {
|
||||||
|
const fill = obj.get("fill");
|
||||||
|
const scale = getTransformScaleAngle(fill?.patternTransform).scale;
|
||||||
|
const pattern = {
|
||||||
|
patternTransform: createPatternTransform(scale, angle),
|
||||||
|
};
|
||||||
|
changeFill(obj, pattern);
|
||||||
|
};
|
||||||
|
// 改变填充偏移
|
||||||
|
const inputFillOffset = (value, obj) => {
|
||||||
|
if (!obj.oldPattern) obj.oldPattern = obj.get("fill");
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
...obj.get("fill"),
|
||||||
|
offsetX: (value.left / 100) * obj.width,
|
||||||
|
offsetY: (value.top / 100) * obj.height,
|
||||||
|
});
|
||||||
|
obj.set("fill", pattern);
|
||||||
|
props.canvas.renderAll();
|
||||||
|
};
|
||||||
|
const changeFillOffset = (value, obj) => {
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
offsetX: (value.left / 100) * obj.width,
|
||||||
|
offsetY: (value.top / 100) * obj.height,
|
||||||
|
});
|
||||||
|
changeFill(obj, pattern);
|
||||||
|
};
|
||||||
|
// 改变填充缩放
|
||||||
|
const inputFillScale = (scale, obj) => {
|
||||||
|
if (!obj.oldPattern) obj.oldPattern = obj.get("fill");
|
||||||
|
const fill = obj.get("fill");
|
||||||
|
const angle = getTransformScaleAngle(fill?.patternTransform).angle;
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
...fill,
|
||||||
|
patternTransform: createPatternTransform(scale, angle),
|
||||||
|
});
|
||||||
|
obj.set("fill", pattern);
|
||||||
|
props.canvas.renderAll();
|
||||||
|
};
|
||||||
|
const changeFillScale = (scale, obj) => {
|
||||||
|
const fill = obj.get("fill");
|
||||||
|
const angle = getTransformScaleAngle(fill?.patternTransform).angle;
|
||||||
|
const pattern = {
|
||||||
|
patternTransform: createPatternTransform(scale, angle),
|
||||||
|
};
|
||||||
|
changeFill(obj, pattern);
|
||||||
|
};
|
||||||
|
const changeFill = (obj, pattern) => {
|
||||||
|
const cmd = new FillRepeatChangeCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
layers: layers,
|
||||||
|
canvasManager: props.canvasManager,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layerId: obj.layerId,
|
||||||
|
newPattern: pattern,
|
||||||
|
});
|
||||||
|
props.commandManager.execute(cmd);
|
||||||
|
};
|
||||||
|
// 改变填充间隙
|
||||||
|
const inputFill_Gap = (gapX, gapY, obj) => {
|
||||||
|
const cmd = new FillRepeatGapChangeCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
layers: layers,
|
||||||
|
canvasManager: props.canvasManager,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layerId: obj.layerId,
|
||||||
|
newGapX: gapX,
|
||||||
|
newGapY: gapY,
|
||||||
|
record: true,
|
||||||
|
});
|
||||||
|
cmd.execute();
|
||||||
|
};
|
||||||
|
const changeFill_Gap = (gapX, gapY, obj) => {
|
||||||
|
if (obj.oldFill_) {
|
||||||
|
obj.fill_ = { ...obj.oldFill_ };
|
||||||
|
delete obj.oldFill_;
|
||||||
|
}
|
||||||
|
const cmd = new FillRepeatGapChangeCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
layers: layers,
|
||||||
|
canvasManager: props.canvasManager,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layerId: obj.layerId,
|
||||||
|
newGapX: gapX,
|
||||||
|
newGapY: gapY,
|
||||||
|
});
|
||||||
|
props.commandManager.execute(cmd);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLayerComposite = (newValue, oldValue, obj, isCmd) => {
|
||||||
|
const cmd = new SetLayerCompositeCommand({
|
||||||
|
canvas: props.canvas,
|
||||||
|
layers: layers,
|
||||||
|
layerManager: props.layerManager,
|
||||||
|
layerId: obj.layerId,
|
||||||
|
newValue: newValue,
|
||||||
|
oldValue: oldValue,
|
||||||
|
});
|
||||||
|
if (isCmd) {
|
||||||
|
props.commandManager.execute(cmd);
|
||||||
|
} else {
|
||||||
|
cmd.execute();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickTest = (obj) => {
|
||||||
|
console.log("==========点击测试", obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新选中对象属性
|
||||||
|
const updateActiveObjects = (arrs, keys, isNumber = true) => {
|
||||||
|
arrs.forEach((v) => {
|
||||||
|
activeObjects.value.forEach((item) => {
|
||||||
|
if (item.id === v.id) {
|
||||||
|
keys.forEach(
|
||||||
|
(key) => (item[key] = isNumber ? Number(v[key]) : v[key])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
activeObjects.value = [...activeObjects.value];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 旋转对象时更新角度
|
||||||
|
const objectRotatingChange = (e) => {
|
||||||
|
const arrs = [];
|
||||||
|
if (e.target._objects) {
|
||||||
|
e.target._objects.forEach((v) => arrs.push(v));
|
||||||
|
} else {
|
||||||
|
arrs.push(e.target);
|
||||||
|
}
|
||||||
|
updateActiveObjects(arrs, ["angle"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 对象属性修改后触发
|
||||||
|
const objectModifiedChange = (e) => {
|
||||||
|
console.log("==========object:modified", e.target);
|
||||||
|
};
|
||||||
|
// 不透明度撤销时触发
|
||||||
|
const objectOpacityUndo = (layerId, opacity) => {
|
||||||
|
const layerObjects = props.canvas
|
||||||
|
.getObjects()
|
||||||
|
.filter((obj) => obj.layerId === layerId);
|
||||||
|
updateActiveObjects(layerObjects, ["opacity"]);
|
||||||
|
};
|
||||||
|
// 对象属性修改撤销时触发
|
||||||
|
const objectModifiedUndo = (object) => {
|
||||||
|
updateActiveObjects([object], ["angle"]);
|
||||||
|
};
|
||||||
|
// 组合操作撤销时触发
|
||||||
|
const objectCompositeChange = (object) => {
|
||||||
|
updateActiveObjects([object], ["globalCompositeOperation"], false);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 设置画布事件监听
|
||||||
|
*/
|
||||||
|
function setupCanvasListeners() {
|
||||||
|
if (!props.canvas) return;
|
||||||
|
// 注册事件
|
||||||
|
props.canvas.on("selection:created", getActiveObject);
|
||||||
|
props.canvas.on("selection:updated", getActiveObject);
|
||||||
|
props.canvas.on("selection:cleared", cancelSelect);
|
||||||
|
props.canvas.on("object:rotating", objectRotatingChange);
|
||||||
|
props.canvas.on("object:modified", objectModifiedChange);
|
||||||
|
EventManager.on("object:opacity:execute", objectOpacityUndo);
|
||||||
|
EventManager.on("object:opacity:undo", objectOpacityUndo);
|
||||||
|
EventManager.on("object:modified:execute", objectModifiedUndo);
|
||||||
|
EventManager.on("object:modified:undo", objectModifiedUndo);
|
||||||
|
EventManager.on("object:composite:execute", objectCompositeChange);
|
||||||
|
EventManager.on("object:composite:undo", objectCompositeChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除画布事件监听
|
||||||
|
*/
|
||||||
|
function removeCanvasListeners() {
|
||||||
|
if (!props.canvas) return;
|
||||||
|
// 移除事件
|
||||||
|
props.canvas.off("selection:created", getActiveObject);
|
||||||
|
props.canvas.off("selection:updated", getActiveObject);
|
||||||
|
props.canvas.off("selection:cleared", cancelSelect);
|
||||||
|
props.canvas.off("object:rotating", objectRotatingChange);
|
||||||
|
props.canvas.off("object:modified", objectModifiedChange);
|
||||||
|
EventManager.off("object:opacity:execute", objectOpacityUndo);
|
||||||
|
EventManager.off("object:opacity:undo", objectOpacityUndo);
|
||||||
|
EventManager.off("object:modified:execute", objectModifiedUndo);
|
||||||
|
EventManager.off("object:modified:undo", objectModifiedUndo);
|
||||||
|
EventManager.off("object:composite:execute", objectCompositeChange);
|
||||||
|
EventManager.off("object:composite:undo", objectCompositeChange);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.select-menu-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 22px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
// max-width: min(90vw, 640px);
|
||||||
|
max-width: 95%;
|
||||||
|
width: 80rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
user-select: none;
|
||||||
|
&.active {
|
||||||
|
transform: translateY(100%);
|
||||||
|
> .btn {
|
||||||
|
> i {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板和手机适配 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.select-menu-panel {
|
||||||
|
bottom: 15px;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
max-width: calc(100vw - 30px);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.select-menu-panel {
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-menu-panel.is-active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-select {
|
||||||
|
// padding: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板适配 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.panel-header {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机适配 */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.panel-header {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn span {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-btn.active {
|
||||||
|
background-color: #007aff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
margin: 0 10px 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 20rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
> .object-item {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 1rem 0;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
> .title {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
> .list {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
> .iconfont {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
> .label {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
> .angle-tool {
|
||||||
|
width: 9rem;
|
||||||
|
}
|
||||||
|
> .tip {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
// margin-left: 0.8rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
display: none;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 97%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-top: 5px solid rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
> .tip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> div.input {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #474747;
|
||||||
|
> .label {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
> .iconfont {
|
||||||
|
margin-right: 0.4rem;
|
||||||
|
}
|
||||||
|
> .slider {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> div.select {
|
||||||
|
> .iconfont {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
> .my-select,
|
||||||
|
> .ant-select {
|
||||||
|
width: 12rem;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> div.btn {
|
||||||
|
min-width: 2.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> div.color {
|
||||||
|
width: 4rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: linear-gradient(to bottom, #ff0000, #ffff00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板适配 - 每行4个按钮 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.tool-content {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px 6px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机适配 - 每行3个按钮 */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.tool-content {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 6px 4px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-btn {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
min-width: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -29,8 +29,12 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
clothingMinIOPath: {
|
||||||
|
type: String,
|
||||||
|
default: "", // 衣服底图URL-线稿
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
console.log(props.clothingMinIOPath)
|
||||||
const commandManager = inject("commandManager");
|
const commandManager = inject("commandManager");
|
||||||
const layerManager = inject("layerManager"); // 图层管理器
|
const layerManager = inject("layerManager"); // 图层管理器
|
||||||
|
|
||||||
@@ -166,6 +170,19 @@ const normalToolsList = ref([
|
|||||||
icon: { name: "CFont", size: "20" },
|
icon: { name: "CFont", size: "20" },
|
||||||
class: "text-btn",
|
class: "text-btn",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: OperationType.PART,
|
||||||
|
title: t("Canvas.GarmentPartSelector"),
|
||||||
|
action: () => selectTool(OperationType.PART),
|
||||||
|
icon: { name: "CPart", size: "28" },
|
||||||
|
class: "part-btn",
|
||||||
|
activeList: [
|
||||||
|
OperationType.PART,
|
||||||
|
OperationType.PART_RECTANGLE,
|
||||||
|
OperationType.PART_BRUSH,
|
||||||
|
OperationType.PART_ERASER,
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "help",
|
id: "help",
|
||||||
title: t("Canvas.help"),
|
title: t("Canvas.help"),
|
||||||
@@ -236,7 +253,13 @@ const redGreenToolsList = ref([
|
|||||||
|
|
||||||
// 根据模式选择工具列表
|
// 根据模式选择工具列表
|
||||||
const toolsList = computed(() => {
|
const toolsList = computed(() => {
|
||||||
return props.isRedGreenMode ? redGreenToolsList.value : normalToolsList.value;
|
const list = props.isRedGreenMode ? redGreenToolsList.value : normalToolsList.value;
|
||||||
|
return list.filter(tool => {
|
||||||
|
if(tool.id === OperationType.PART){
|
||||||
|
return !!props.clothingMinIOPath;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function selectTool(tool, isRedGreenMode = false) {
|
function selectTool(tool, isRedGreenMode = false) {
|
||||||
@@ -412,8 +435,12 @@ const handleToolClick = (tool) => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
.tools-list::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.red-green-mode {
|
.red-green-mode {
|
||||||
background-color: #fff4f4;
|
background-color: #060505;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-indicator {
|
.mode-indicator {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
overflow-y: auto;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
.layers-header {
|
.layers-header {
|
||||||
@@ -132,10 +132,11 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
.layers-list-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
.layers-list {
|
.layers-list {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
.layer-item {
|
.layer-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -270,6 +271,13 @@
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
.layer-color-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
.layer-actions {
|
.layer-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
|
|||||||
167
src/component/Canvas/CanvasEditor/components/tools/AngleTool.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<div class="angle-tool" :disabled="disabled">
|
||||||
|
<template v-if="styleType === '1'">
|
||||||
|
<div
|
||||||
|
ref="dishRef"
|
||||||
|
class="dish"
|
||||||
|
@mousedown.stop="mousedown"
|
||||||
|
@touchmove.stop="mousedown"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="pointer"
|
||||||
|
:style="{ transform: `rotate(${angle}deg)` }"
|
||||||
|
>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="angle"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<my-input
|
||||||
|
v-if="styleType === '2'"
|
||||||
|
v-model="angle"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
type="number"
|
||||||
|
after="°"
|
||||||
|
icon="icon-angle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||||
|
import { calculateAngle } from "../../utils/helper";
|
||||||
|
import MyInput from "./MyInput.vue";
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
styleType: {
|
||||||
|
type: String,
|
||||||
|
default: "1",
|
||||||
|
},
|
||||||
|
angle: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["change", "input"]);
|
||||||
|
const angle = ref(props.angle);
|
||||||
|
watch(
|
||||||
|
() => props.angle,
|
||||||
|
(value) => {
|
||||||
|
angle.value = value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const dishRef = ref<HTMLDivElement>();
|
||||||
|
const mousedown = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
const mousemove = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!dishRef.value) return;
|
||||||
|
const { left, top, width, height } =
|
||||||
|
dishRef.value.getBoundingClientRect();
|
||||||
|
const centerX = left + width / 2;
|
||||||
|
const centerY = top + height / 2;
|
||||||
|
const { clientX, clientY } = e?.touches?.[0] || e;
|
||||||
|
angle.value = calculateAngle(centerX, centerY, clientX, clientY, true);
|
||||||
|
onInput();
|
||||||
|
};
|
||||||
|
mousemove(e);
|
||||||
|
const mouseup = () => {
|
||||||
|
onChange();
|
||||||
|
document.removeEventListener("mousemove", mousemove);
|
||||||
|
document.removeEventListener("touchmove", mousemove);
|
||||||
|
document.removeEventListener("mouseup", mouseup);
|
||||||
|
document.removeEventListener("touchend", mouseup);
|
||||||
|
};
|
||||||
|
document.addEventListener("mousemove", mousemove);
|
||||||
|
document.addEventListener("touchmove", mousemove);
|
||||||
|
document.addEventListener("mouseup", mouseup);
|
||||||
|
document.addEventListener("touchend", mouseup);
|
||||||
|
};
|
||||||
|
const onInput = () => !props.disabled && emit("input", angle.value);
|
||||||
|
var changeTime: any = null;
|
||||||
|
const onChange = () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
clearTimeout(changeTime);
|
||||||
|
changeTime = setTimeout(() => emit("change", angle.value), 500);
|
||||||
|
};
|
||||||
|
// var angleTime = null;
|
||||||
|
// watch(angle, (value) => {
|
||||||
|
// emit("input", value);
|
||||||
|
// clearTimeout(angleTime);
|
||||||
|
// angleTime = setTimeout(() => emit("change", value), 50);
|
||||||
|
// });
|
||||||
|
// defineExpose({
|
||||||
|
// open,
|
||||||
|
// close,
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.angle-tool {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
--color: #000;
|
||||||
|
&[disabled="true"] {
|
||||||
|
--color: #b2b2b2;
|
||||||
|
> .dish {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .dish {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: 1px solid var(--color);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
> .pointer {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
> span {
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 35%;
|
||||||
|
height: 35%;
|
||||||
|
background-color: var(--color);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .input {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color);
|
||||||
|
flex: 1;
|
||||||
|
// min-width: 45px;
|
||||||
|
// max-width: 80px;
|
||||||
|
// width: 50px;
|
||||||
|
> input {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .my-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="my-input">
|
||||||
|
<span class="decorate"></span>
|
||||||
|
<span v-show="icon" :class="['iconfont', icon]"></span>
|
||||||
|
<span v-show="before" class="before">{{ before }}</span>
|
||||||
|
<input v-bind="$attrs" :value="modelValue" @input="onInput" />
|
||||||
|
<span v-show="after" class="after">{{ after }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Number, default: 0 },
|
||||||
|
icon: { default: "", type: String },
|
||||||
|
before: { default: "", type: String },
|
||||||
|
after: { default: "", type: String },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "input"]);
|
||||||
|
const onInput = (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
emit("input", value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.my-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(230, 230, 231, 1);
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 4px 0 2px;
|
||||||
|
> .decorate {
|
||||||
|
width: 2px;
|
||||||
|
background-color: rgba(230, 230, 231, 1);
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 85%;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
> .iconfont {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #000;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
> .before {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
> .after {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
> input {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 0;
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
class="my-select"
|
||||||
|
:size="size"
|
||||||
|
@change="change"
|
||||||
|
:defaultValue="defaultValue"
|
||||||
|
@dropdownVisibleChange="dropdownVisibleChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="v in list"
|
||||||
|
:key="v.value"
|
||||||
|
:value="v.value"
|
||||||
|
:title="v.tip"
|
||||||
|
@mouseover.stop.prevent="mouseover(v)"
|
||||||
|
@mouseleave="mouseleave(v)"
|
||||||
|
>{{ v.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||||
|
const props = defineProps({
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: "small",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["change", "active"]);
|
||||||
|
const isChange = ref(false);
|
||||||
|
const initValue = ref(props.defaultValue);
|
||||||
|
const activeValue = ref(props.defaultValue);
|
||||||
|
const timeout = ref(null);
|
||||||
|
const mouseover = (v) => {
|
||||||
|
clearTimeout(timeout.value);
|
||||||
|
if (v.value === activeValue.value) return;
|
||||||
|
emit("active", v.value, activeValue.value);
|
||||||
|
activeValue.value = v.value;
|
||||||
|
};
|
||||||
|
const mouseleave = () => {
|
||||||
|
clearTimeout(timeout.value);
|
||||||
|
timeout.value = setTimeout(() => {
|
||||||
|
dropdownVisibleChange(false);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
const change = (v) => {
|
||||||
|
isChange.value = true;
|
||||||
|
emit("change", v, initValue.value);
|
||||||
|
};
|
||||||
|
const dropdownVisibleChange = (v) => {
|
||||||
|
if (v) {
|
||||||
|
isChange.value = false;
|
||||||
|
initValue.value = props.defaultValue;
|
||||||
|
} else if (!isChange.value) {
|
||||||
|
emit("active", initValue.value, activeValue.value);
|
||||||
|
activeValue.value = initValue.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div class="offset-tool">
|
||||||
|
<div class="input" v-show="showInput">
|
||||||
|
<my-input
|
||||||
|
v-model="left"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
type="number"
|
||||||
|
before="X"
|
||||||
|
after="%"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
/>
|
||||||
|
<my-input
|
||||||
|
v-model="top"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
type="number"
|
||||||
|
before="Y"
|
||||||
|
after="%"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dish"
|
||||||
|
@mousedown="mousedown"
|
||||||
|
@touchstart="mousedown"
|
||||||
|
ref="dishRef"
|
||||||
|
v-show="showDish"
|
||||||
|
>
|
||||||
|
<img src="/src/assets/images/icon/xyz.png" />
|
||||||
|
<span class="ball" :style="ballStyle"></span>
|
||||||
|
<span class="tip x">X: {{ left }}%</span>
|
||||||
|
<span class="tip y">Y: {{ top }}%</span>
|
||||||
|
<span class="line x"></span>
|
||||||
|
<span class="line y"></span>
|
||||||
|
<span class="line z" :style="lineZStyle"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, watch, computed } from "vue";
|
||||||
|
import MyInput from "./MyInput.vue";
|
||||||
|
const props = defineProps({
|
||||||
|
left: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
showInput: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showDish: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["change", "input"]);
|
||||||
|
// 工具的实际坐标 -100 ~ 100
|
||||||
|
const top = ref(Math.round(props.top));
|
||||||
|
const left = ref(Math.round(props.left));
|
||||||
|
|
||||||
|
// 原点的坐标 0 ~ 100
|
||||||
|
const ballStyle = computed(() => ({
|
||||||
|
top: 50 + Number(top.value) / 2 + "%",
|
||||||
|
left: 50 + Number(left.value) / 2 + "%",
|
||||||
|
}));
|
||||||
|
watch(
|
||||||
|
() => props.left,
|
||||||
|
(v) => (left.value = Math.round(v))
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.top,
|
||||||
|
(v) => (top.value = Math.round(v))
|
||||||
|
);
|
||||||
|
const dishRef = ref<HTMLDivElement>();
|
||||||
|
const mousedown = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!dishRef.value) return;
|
||||||
|
const mousemove = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!dishRef.value) return;
|
||||||
|
const rect = dishRef.value.getBoundingClientRect();
|
||||||
|
const X = e.clientX || (e as TouchEvent).touches[0].clientX;
|
||||||
|
const Y = e.clientY || (e as TouchEvent).touches[0].clientY;
|
||||||
|
var x = ((X - rect.left) / rect.width) * 100;
|
||||||
|
var y = ((Y - rect.top) / rect.height) * 100;
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
if (x > 100) x = 100;
|
||||||
|
if (y < 0) y = 0;
|
||||||
|
if (y > 100) y = 100;
|
||||||
|
left.value = Math.round((x - 50) * 2);
|
||||||
|
top.value = Math.round((y - 50) * 2);
|
||||||
|
onInput();
|
||||||
|
};
|
||||||
|
mousemove(e);
|
||||||
|
const mouseup = () => {
|
||||||
|
onChange();
|
||||||
|
document.removeEventListener("mousemove", mousemove);
|
||||||
|
document.removeEventListener("touchmove", mousemove);
|
||||||
|
document.removeEventListener("mouseup", mouseup);
|
||||||
|
document.removeEventListener("touchend", mouseup);
|
||||||
|
};
|
||||||
|
document.addEventListener("mousemove", mousemove);
|
||||||
|
document.addEventListener("touchmove", mousemove);
|
||||||
|
document.addEventListener("mouseup", mouseup);
|
||||||
|
document.addEventListener("touchend", mouseup);
|
||||||
|
};
|
||||||
|
const onInput = () => {
|
||||||
|
emit("input", { left: left.value, top: top.value });
|
||||||
|
};
|
||||||
|
var changeTime: any = null;
|
||||||
|
const onChange = () => {
|
||||||
|
clearTimeout(changeTime);
|
||||||
|
changeTime = setTimeout(() => {
|
||||||
|
emit("change", {
|
||||||
|
left: left.value,
|
||||||
|
top: top.value,
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
const lineZStyle = computed(() => ({
|
||||||
|
"--rotateZ": calculateAngle(0, 0, left.value, top.value) + "deg",
|
||||||
|
width: calculateDistance(0, 0, left.value, top.value) / 2 + "%",
|
||||||
|
}));
|
||||||
|
// 计算角度
|
||||||
|
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
|
||||||
|
const deltaX = x2 - x1;
|
||||||
|
const deltaY = y1 - y2;
|
||||||
|
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90;
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
// 计算距离
|
||||||
|
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
|
||||||
|
const deltaX = x2 - x1;
|
||||||
|
const deltaY = y2 - y1;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.offset-tool {
|
||||||
|
position: relative;
|
||||||
|
> .input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 12px;
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .dish {
|
||||||
|
width: 135px;
|
||||||
|
height: 135px;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
margin-top: 24px;
|
||||||
|
> * {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
> img {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
> .ball {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0px 0.68px 1.7px 0px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
> .tip {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #000;
|
||||||
|
line-height: 24px;
|
||||||
|
&.x {
|
||||||
|
top: 50%;
|
||||||
|
right: 0%;
|
||||||
|
transform: translate(100%, -50%);
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
&.y {
|
||||||
|
top: 0%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .line {
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
border-style: dashed;
|
||||||
|
border-width: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
&.x {
|
||||||
|
width: 100%;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
&.y {
|
||||||
|
height: 100%;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
&.z {
|
||||||
|
width: 50%;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-color: #454754;
|
||||||
|
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
173
src/component/Canvas/CanvasEditor/components/tools/Slider.vue
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<div class="slider" :disabled="disabled">
|
||||||
|
<div
|
||||||
|
class="input-range"
|
||||||
|
:style="{
|
||||||
|
'--progress': (value - props.min) / (props.max - props.min),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="tip">{{ props.tipFormatter(value) }}</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
v-model="value"
|
||||||
|
:min="props.min"
|
||||||
|
:max="props.max"
|
||||||
|
:step="props.step"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="input" v-show="isInput">
|
||||||
|
<my-input
|
||||||
|
v-model="value"
|
||||||
|
:min="props.min"
|
||||||
|
:max="props.max"
|
||||||
|
:step="props.step"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||||
|
import MyInput from "./MyInput.vue";
|
||||||
|
const props = defineProps({
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
tipFormatter: {
|
||||||
|
type: Function,
|
||||||
|
default: (v) => v,
|
||||||
|
},
|
||||||
|
isInput: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["change", "input"]);
|
||||||
|
const value = ref(props.value);
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(v) => (value.value = v)
|
||||||
|
);
|
||||||
|
const onInput = () => !props.disabled && emit("input", Number(value.value));
|
||||||
|
var changeTime: any = null;
|
||||||
|
const onChange = () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
clearTimeout(changeTime);
|
||||||
|
changeTime = setTimeout(() => emit("change", Number(value.value)), 500);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 150px;
|
||||||
|
--input-thumb-size: 10px;
|
||||||
|
--backcolor1: var(--slider-thumb-color1, #4285f4);
|
||||||
|
--backcolor2: var(--slider-thumb-color2, rgba(0, 0, 0, 0.1));
|
||||||
|
&:hover {
|
||||||
|
> .input-range > .tip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .input-range {
|
||||||
|
position: relative;
|
||||||
|
flex: 2;
|
||||||
|
> input {
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
outline: none;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--backcolor1) 0%,
|
||||||
|
var(--backcolor1) calc(var(--progress) * 100%),
|
||||||
|
var(--backcolor2) calc(var(--progress) * 100%),
|
||||||
|
var(--backcolor2) 100%
|
||||||
|
);
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: var(--input-thumb-size);
|
||||||
|
height: var(--input-thumb-size);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--backcolor1); /* 蓝色滑块 */
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
&::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .tip {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 10px;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
color: #666;
|
||||||
|
top: 0;
|
||||||
|
left: calc(
|
||||||
|
(100% - var(--input-thumb-size)) * var(--progress) +
|
||||||
|
var(--input-thumb-size) / 2
|
||||||
|
);
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
display: none;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 97%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-top: 5px solid rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .input {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10px;
|
||||||
|
> input {
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,6 +17,7 @@ import { KeyboardManager } from "./managers/events/KeyboardManager.js";
|
|||||||
import CanvasConfig from "./config/canvasConfig.js";
|
import CanvasConfig from "./config/canvasConfig.js";
|
||||||
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
|
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
|
||||||
import { SelectionManager } from "./managers/selection/SelectionManager";
|
import { SelectionManager } from "./managers/selection/SelectionManager";
|
||||||
|
import { PartManager } from "./managers/PartManager";
|
||||||
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
||||||
import texturePresetManager from "./managers/brushes/TexturePresetManager";
|
import texturePresetManager from "./managers/brushes/TexturePresetManager";
|
||||||
import { BrushStore } from "./store/BrushStore";
|
import { BrushStore } from "./store/BrushStore";
|
||||||
@@ -35,8 +36,10 @@ import LayersPanel from "./components/LayersPanel/LayersPanel.vue";
|
|||||||
import BrushControlPanel from "./components/BrushControlPanel.vue";
|
import BrushControlPanel from "./components/BrushControlPanel.vue";
|
||||||
import TextEditorPanel from "./components/TextEditorPanel.vue"; // 引入文本编辑面板
|
import TextEditorPanel from "./components/TextEditorPanel.vue"; // 引入文本编辑面板
|
||||||
import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑面板
|
import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑面板
|
||||||
import SelectMenuPanel from "./components/SelectMenuPanel.vue"; // 引入选择工具菜单组件
|
import PalletPanel from "./components/PalletPanel/index.vue";
|
||||||
|
import SelectMenuPanel from "./components/SelectMenuPanel/index.vue"; // 引入选择工具菜单组件
|
||||||
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
|
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
|
||||||
|
import PartSelectorPanel from "./components/PartSelectorPanel.vue"; // 引入部件选取面板
|
||||||
import { LayerType, OperationType } from "./utils/layerHelper.js";
|
import { LayerType, OperationType } from "./utils/layerHelper.js";
|
||||||
import { ToolManager } from "./managers/ToolManager.js";
|
import { ToolManager } from "./managers/ToolManager.js";
|
||||||
import { fabric } from "fabric-with-all";
|
import { fabric } from "fabric-with-all";
|
||||||
@@ -45,6 +48,7 @@ import {
|
|||||||
loadImageUrlToLayer,
|
loadImageUrlToLayer,
|
||||||
loadImage,
|
loadImage,
|
||||||
} from "./utils/imageHelper.js";
|
} from "./utils/imageHelper.js";
|
||||||
|
import { optimizeCanvasRendering } from "./utils/helper";
|
||||||
// import MinimapPanel from "./components/MinimapPanel.vue";
|
// import MinimapPanel from "./components/MinimapPanel.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -56,7 +60,9 @@ const emit = defineEmits([
|
|||||||
"trigger-red-green-mouseup", // 红绿图模式鼠标抬起事件
|
"trigger-red-green-mouseup", // 红绿图模式鼠标抬起事件
|
||||||
"changeCanvas", // 画布变更事件
|
"changeCanvas", // 画布变更事件
|
||||||
"canvasInit", // 画布初始化事件
|
"canvasInit", // 画布初始化事件
|
||||||
|
"canvas-load-json-success", // 画布加载JSON成功事件
|
||||||
"trigger-library", // 触发打开Library选择图片事件
|
"trigger-library", // 触发打开Library选择图片事件
|
||||||
|
"before-unmount-export-extra-info", // 组件卸载前导出额外信息事件
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -64,6 +70,10 @@ const props = defineProps({
|
|||||||
type: [Object, String],
|
type: [Object, String],
|
||||||
default: "", // 默认空
|
default: "", // 默认空
|
||||||
},
|
},
|
||||||
|
otherData: {
|
||||||
|
type: [Object, null],
|
||||||
|
default: null, // 默认空对象
|
||||||
|
},
|
||||||
config: {
|
config: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => CanvasConfig, // 默认配置
|
default: () => CanvasConfig, // 默认配置
|
||||||
@@ -76,9 +86,17 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false, // 是否启用红绿图模式
|
default: false, // 是否启用红绿图模式
|
||||||
},
|
},
|
||||||
|
clothingMinIOPath: {
|
||||||
|
type: String,
|
||||||
|
default: "", // 衣服底图URL-线稿
|
||||||
|
},
|
||||||
clothingImageUrl: {
|
clothingImageUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "", // 衣服底图URL
|
default: "", // 衣服底图URL-线稿
|
||||||
|
},
|
||||||
|
clothingImageUrl2: {
|
||||||
|
type: String,
|
||||||
|
default: "", // 衣服底图URL-上色
|
||||||
},
|
},
|
||||||
redGreenImageUrl: {
|
redGreenImageUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -172,6 +190,7 @@ let keyboardManager = null;
|
|||||||
let toolManager = null;
|
let toolManager = null;
|
||||||
let liquifyManager = null;
|
let liquifyManager = null;
|
||||||
let selectionManager = null;
|
let selectionManager = null;
|
||||||
|
let partManager = null;
|
||||||
let redGreenModeManager = null;
|
let redGreenModeManager = null;
|
||||||
|
|
||||||
// 快捷键帮助模态框状态
|
// 快捷键帮助模态框状态
|
||||||
@@ -213,6 +232,7 @@ function handleCanvasInit(isLoadJson = false) {
|
|||||||
keyboardManager,
|
keyboardManager,
|
||||||
liquifyManager,
|
liquifyManager,
|
||||||
selectionManager,
|
selectionManager,
|
||||||
|
partManager,
|
||||||
redGreenModeManager,
|
redGreenModeManager,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -250,6 +270,8 @@ onMounted(async () => {
|
|||||||
canvasColor,
|
canvasColor,
|
||||||
enabledRedGreenMode: props.enabledRedGreenMode,
|
enabledRedGreenMode: props.enabledRedGreenMode,
|
||||||
isFixedErasable: props.isFixedErasable,
|
isFixedErasable: props.isFixedErasable,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
});
|
});
|
||||||
canvasManager.canvas.activeLayerId = activeLayerId;
|
canvasManager.canvas.activeLayerId = activeLayerId;
|
||||||
canvasManager.activeLayerId = activeLayerId;
|
canvasManager.activeLayerId = activeLayerId;
|
||||||
@@ -307,6 +329,7 @@ onMounted(async () => {
|
|||||||
canvas: canvasManager.canvas,
|
canvas: canvasManager.canvas,
|
||||||
commandManager,
|
commandManager,
|
||||||
layerManager,
|
layerManager,
|
||||||
|
canvasManager,
|
||||||
toolManager,
|
toolManager,
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
pasteText: (text) => {
|
pasteText: (text) => {
|
||||||
@@ -359,9 +382,20 @@ onMounted(async () => {
|
|||||||
selectionManager = new SelectionManager({
|
selectionManager = new SelectionManager({
|
||||||
canvas: canvasManager.canvas,
|
canvas: canvasManager.canvas,
|
||||||
layerManager,
|
layerManager,
|
||||||
|
props,
|
||||||
});
|
});
|
||||||
canvasManager.setSelectionManager(selectionManager);
|
canvasManager.setSelectionManager(selectionManager);
|
||||||
|
|
||||||
|
// 初始化部件选择管理器
|
||||||
|
partManager = new PartManager({
|
||||||
|
canvas: canvasManager.canvas,
|
||||||
|
layerManager,
|
||||||
|
canvasManager,
|
||||||
|
toolManager,
|
||||||
|
props,
|
||||||
|
});
|
||||||
|
canvasManager.setPartManager(partManager);
|
||||||
|
|
||||||
if (props.canvasJSON) {
|
if (props.canvasJSON) {
|
||||||
// 如果传入了初始JSON数据,加载到画布上
|
// 如果传入了初始JSON数据,加载到画布上
|
||||||
if (typeof props.canvasJSON === "string") {
|
if (typeof props.canvasJSON === "string") {
|
||||||
@@ -435,6 +469,12 @@ onMounted(async () => {
|
|||||||
canvasManager.canvas.width,
|
canvasManager.canvas.width,
|
||||||
canvasManager.canvas.height
|
canvasManager.canvas.height
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(props.otherData && !props.otherData.canvasId) {
|
||||||
|
await canvasManager?.createOtherLayers(props.otherData);
|
||||||
|
await layerManager?.layerSort?.rearrangeObjects();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 设置固定图层是否可擦除
|
// // 设置固定图层是否可擦除
|
||||||
@@ -464,38 +504,13 @@ onMounted(async () => {
|
|||||||
}, 700);
|
}, 700);
|
||||||
});
|
});
|
||||||
|
|
||||||
let throttleTimeout = null;
|
|
||||||
let lastRunTime = 0;
|
|
||||||
let trailingTimeout = null;
|
let trailingTimeout = null;
|
||||||
|
let throttleDelay = 100;
|
||||||
observer = new ResizeObserver((entries) => {
|
observer = new ResizeObserver((entries) => {
|
||||||
const now = Date.now();
|
clearTimeout(trailingTimeout);
|
||||||
const throttleDelay = 100;
|
trailingTimeout = setTimeout(() => {
|
||||||
|
optimizeCanvasRendering(canvasManager.canvas, ()=> handleWindowResize());
|
||||||
if (!throttleTimeout) {
|
}, throttleDelay);
|
||||||
// 立即执行一次
|
|
||||||
handleWindowResize();
|
|
||||||
layerManager?.updateLayersObjectsInteractivity?.();
|
|
||||||
setTimeout(() => {
|
|
||||||
layerManager?.updateLayersObjectsInteractivity?.();
|
|
||||||
});
|
|
||||||
lastRunTime = now;
|
|
||||||
|
|
||||||
throttleTimeout = setTimeout(() => {
|
|
||||||
throttleTimeout = null;
|
|
||||||
}, throttleDelay);
|
|
||||||
} else {
|
|
||||||
// 如果在节流期间有新的变化,则重置尾触发
|
|
||||||
clearTimeout(trailingTimeout);
|
|
||||||
trailingTimeout = setTimeout(() => {
|
|
||||||
handleWindowResize();
|
|
||||||
layerManager?.updateLayersObjectsInteractivity?.();
|
|
||||||
setTimeout(() => {
|
|
||||||
layerManager?.updateLayersObjectsInteractivity?.();
|
|
||||||
});
|
|
||||||
lastRunTime = Date.now();
|
|
||||||
}, throttleDelay);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
observer.observe(canvasContainerRef.value);
|
observer.observe(canvasContainerRef.value);
|
||||||
// 使用window的resize事件代替ResizeObserver
|
// 使用window的resize事件代替ResizeObserver
|
||||||
@@ -527,12 +542,11 @@ watchEffect(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(async () => {
|
||||||
// if (import.meta.hot) {
|
observer.unobserve(canvasContainerRef.value);
|
||||||
// // 热更新 ?
|
// const extraInfo = await canvasManager.exportExtraInfo();
|
||||||
// console.log("onBeforeUnmount 开发环境热更新不卸载组件...");
|
// emit("before-unmount-export-extra-info", extraInfo);
|
||||||
// return; // 开发环境下不卸载组件
|
|
||||||
// }
|
|
||||||
console.log("onBeforeUnmount 组件卸载,清理资源...");
|
console.log("onBeforeUnmount 组件卸载,清理资源...");
|
||||||
canvasManager?.dispose?.();
|
canvasManager?.dispose?.();
|
||||||
commandManager?.dispose?.();
|
commandManager?.dispose?.();
|
||||||
@@ -541,6 +555,7 @@ onBeforeUnmount(() => {
|
|||||||
toolManager?.dispose?.();
|
toolManager?.dispose?.();
|
||||||
liquifyManager?.dispose?.();
|
liquifyManager?.dispose?.();
|
||||||
selectionManager?.dispose?.();
|
selectionManager?.dispose?.();
|
||||||
|
partManager?.dispose?.();
|
||||||
redGreenModeManager?.dispose?.();
|
redGreenModeManager?.dispose?.();
|
||||||
// minimapManager?.dispose?.();
|
// minimapManager?.dispose?.();
|
||||||
canvasManager = null;
|
canvasManager = null;
|
||||||
@@ -550,25 +565,25 @@ onBeforeUnmount(() => {
|
|||||||
toolManager = null;
|
toolManager = null;
|
||||||
liquifyManager = null;
|
liquifyManager = null;
|
||||||
selectionManager = null;
|
selectionManager = null;
|
||||||
|
partManager = null;
|
||||||
redGreenModeManager = null;
|
redGreenModeManager = null;
|
||||||
// fabric.Object.prototype.controls.deleteControl = undefined;
|
// fabric.Object.prototype.controls.deleteControl = undefined;
|
||||||
|
|
||||||
// 移除window resize事件监听
|
// 移除window resize事件监听
|
||||||
// window.removeEventListener("resize", handleWindowResize);
|
// window.removeEventListener("resize", handleWindowResize);
|
||||||
observer.unobserve(canvasContainerRef.value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 窗口大小变化处理函数
|
// 窗口大小变化处理函数
|
||||||
function handleWindowResize() {
|
async function handleWindowResize() {
|
||||||
console.log(132);
|
console.log("==========画布窗口大小变化==========");
|
||||||
// 使用requestAnimationFrame来防止频繁更新
|
// 使用requestAnimationFrame来防止频繁更新
|
||||||
setTimeout(() => {
|
await new Promise(requestAnimationFrame);
|
||||||
// 更新画布大小并自动居中所有元素
|
if(!canvasManager) return;
|
||||||
updateCanvasSize();
|
updateCanvasSize();
|
||||||
|
// 确保显示的缩放信息是最新的
|
||||||
// 确保显示的缩放信息是最新的
|
currentZoom.value = Math.round(canvasManager.canvas.getZoom() * 100);
|
||||||
currentZoom.value = Math.round(canvasManager.canvas.getZoom() * 100);
|
await new Promise(requestAnimationFrame);
|
||||||
});
|
await layerManager?.updateLayersObjectsInteractivity?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetZoom() {
|
function resetZoom() {
|
||||||
@@ -715,47 +730,17 @@ function addRemoveBtn(fun) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFun() {
|
function deleteFun(e, control) {
|
||||||
removeLayer(layerManager.activeLayerId.value);
|
const target = control.target;
|
||||||
|
if(target.onDelete){
|
||||||
|
target.onDelete(target);
|
||||||
|
}else if(target.id){
|
||||||
|
removeLayer(layerManager.activeLayerId.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLayer(layerId) {
|
function removeLayer(layerId) {
|
||||||
// Check if this is the last layer - prevent deletion
|
|
||||||
var isChild = false;
|
|
||||||
var parentLength = 0;
|
|
||||||
layers.value.forEach((layer) => {
|
|
||||||
if(layer.children.some(v => v.id == layerId)){
|
|
||||||
isChild = true;
|
|
||||||
parentLength = layer.children.length;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if(isChild && parentLength == 1 || layers.value.length <= 3){
|
|
||||||
console.warn(
|
|
||||||
"Cannot delete the last layer. At least one layer must remain."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
layerManager.removeLayer(layerId);
|
layerManager.removeLayer(layerId);
|
||||||
// 此处删除画布上内容导致撤回操作无效(多余)
|
|
||||||
// if (canvasManager && canvasManager.canvas) {
|
|
||||||
// const layerToRemove = layers.value.find((l) => l.id === layerId);
|
|
||||||
// if (layerToRemove) {
|
|
||||||
// const elementIds = layerToRemove?.fabricObjects?.map((e) => e.id);
|
|
||||||
// elementIds.forEach((elementId) => {
|
|
||||||
// const objectToRemove = canvasManager.canvas
|
|
||||||
// .getObjects()
|
|
||||||
// .find((obj) => obj.id === elementId);
|
|
||||||
// if (objectToRemove) {
|
|
||||||
// canvasManager.canvas.remove(objectToRemove);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// if (activeLayerId.value === layerId) {
|
|
||||||
// activeElementId.value = null;
|
|
||||||
// }
|
|
||||||
// canvasManager.canvas.renderAll();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerImageUpload() {
|
function triggerImageUpload() {
|
||||||
@@ -902,13 +887,18 @@ const changeCanvas = async (command) => {
|
|||||||
...command, // 传递完整的命令数据
|
...command, // 传递完整的命令数据
|
||||||
};
|
};
|
||||||
emit("changeCanvas", commandData);
|
emit("changeCanvas", commandData);
|
||||||
if (command.canUndo || command.canRedo) {
|
canvasManager.changeCanvas(commandData);
|
||||||
|
if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const imageData = await canvasManager.exportImage({
|
try {
|
||||||
restoreOpacityInRedGreen: true, // 恢复红绿图模式下的透明度
|
const imageData = await canvasManager.exportImage({
|
||||||
isCropByBg: true,
|
restoreOpacityInRedGreen: true, // 恢复红绿图模式下的透明度
|
||||||
});
|
isCropByBg: true,
|
||||||
emit("trigger-red-green-mouseup", imageData);
|
});
|
||||||
|
emit("trigger-red-green-mouseup", imageData);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -918,6 +908,14 @@ const cropImage = (url) => {
|
|||||||
return cropImageRef.value.open(url)
|
return cropImageRef.value.open(url)
|
||||||
};
|
};
|
||||||
provide("cropImage", cropImage); // 提供给子组件使用
|
provide("cropImage", cropImage); // 提供给子组件使用
|
||||||
|
// 颜色选择器组件
|
||||||
|
const palletPanelRef = ref(null);
|
||||||
|
const palletPanel = (url) => {
|
||||||
|
return palletPanelRef.value.open(url)
|
||||||
|
};
|
||||||
|
provide("palletPanel", palletPanel); // 提供给子组件使用
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 处理画布容器的拖放事件
|
// 处理画布容器的拖放事件
|
||||||
const isDragOver = ref(false);
|
const isDragOver = ref(false);
|
||||||
@@ -982,6 +980,18 @@ defineExpose({
|
|||||||
...opts,
|
...opts,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateOtherLayers: async (otherData) => {
|
||||||
|
await new Promise((resolve) => optimizeCanvasRendering(canvasManager.canvas, resolve));
|
||||||
|
await canvasManager?.createOtherLayers?.(otherData, true);
|
||||||
|
layerManager.activeLayerId.value = ""
|
||||||
|
layerManager?.sortLayers();
|
||||||
|
await layerManager?.updateLayersObjectsInteractivity?.(true);
|
||||||
|
canvasManager?.canvas?.renderAll();
|
||||||
|
setTimeout(() => {
|
||||||
|
canvasManager?.updateAllThumbnails();
|
||||||
|
}, 500);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
//图片url或者base64
|
//图片url或者base64
|
||||||
addImageToLayer: async (
|
addImageToLayer: async (
|
||||||
url,
|
url,
|
||||||
@@ -1014,6 +1024,10 @@ defineExpose({
|
|||||||
exportImage: ({
|
exportImage: ({
|
||||||
isContainBg = false, // 是否包含背景图层
|
isContainBg = false, // 是否包含背景图层
|
||||||
isContainFixed = false, // 是否包含固定图层
|
isContainFixed = false, // 是否包含固定图层
|
||||||
|
isContainFixedOther = true, // 是否包含其他固定图层--颜色图层
|
||||||
|
isPrintTrimsNoRepeat = true, // 是否包含印花图层的不平铺
|
||||||
|
isPrintTrimsRepeat = true, // 是否包含印花图层的平铺
|
||||||
|
isContainNormalLayer = true, // 是否包含普通图层
|
||||||
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小
|
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小
|
||||||
layerId = "", // 导出具体图层ID
|
layerId = "", // 导出具体图层ID
|
||||||
layerIdArray = [], // 导出多个图层ID数组
|
layerIdArray = [], // 导出多个图层ID数组
|
||||||
@@ -1023,6 +1037,10 @@ defineExpose({
|
|||||||
return canvasManager.exportImage({
|
return canvasManager.exportImage({
|
||||||
isContainBg,
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
|
isContainFixedOther,
|
||||||
|
isPrintTrimsNoRepeat,
|
||||||
|
isPrintTrimsRepeat,
|
||||||
|
isContainNormalLayer,
|
||||||
isCropByBg,
|
isCropByBg,
|
||||||
layerId,
|
layerId,
|
||||||
layerIdArray,
|
layerIdArray,
|
||||||
@@ -1030,6 +1048,10 @@ defineExpose({
|
|||||||
isEnhanceImg,
|
isEnhanceImg,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// 导出颜色图层
|
||||||
|
exportColorLayer: () => {
|
||||||
|
return canvasManager.exportColorLayer();
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 移动图层位置
|
* 移动图层位置
|
||||||
* @param {string} layerId 图层ID
|
* @param {string} layerId 图层ID
|
||||||
@@ -1048,6 +1070,14 @@ defineExpose({
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出所有信息
|
||||||
|
* @returns {Object} 包含所有图层信息的对象
|
||||||
|
*/
|
||||||
|
exportExtraInfo: () => {
|
||||||
|
return canvasManager.exportExtraInfo();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拖拽排序图层
|
* 拖拽排序图层
|
||||||
* @param {number} oldIndex 原索引
|
* @param {number} oldIndex 原索引
|
||||||
@@ -1179,6 +1209,7 @@ defineExpose({
|
|||||||
v-if="canvasManagerLoaded"
|
v-if="canvasManagerLoaded"
|
||||||
:activeTool="activeTool"
|
:activeTool="activeTool"
|
||||||
:isRedGreenMode="isRedGreenMode"
|
:isRedGreenMode="isRedGreenMode"
|
||||||
|
:clothingMinIOPath="props.clothingMinIOPath"
|
||||||
@tool-selected="handleToolSelect"
|
@tool-selected="handleToolSelect"
|
||||||
@red-green-tool-selected="handleRedGreenToolSelect"
|
@red-green-tool-selected="handleRedGreenToolSelect"
|
||||||
@toggle-red-green-mode="toggleRedGreenMode"
|
@toggle-red-green-mode="toggleRedGreenMode"
|
||||||
@@ -1245,6 +1276,20 @@ defineExpose({
|
|||||||
:commandManager="commandManager"
|
:commandManager="commandManager"
|
||||||
:selectionManager="selectionManager"
|
:selectionManager="selectionManager"
|
||||||
:layerManager="layerManager"
|
:layerManager="layerManager"
|
||||||
|
:canvasManager="canvasManager"
|
||||||
|
:toolManager="toolManager"
|
||||||
|
:activeTool="activeTool"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 部件选取面板 -->
|
||||||
|
<PartSelectorPanel
|
||||||
|
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||||
|
:canvas="canvasManager && canvasManager.canvas"
|
||||||
|
:commandManager="commandManager"
|
||||||
|
:selectionManager="selectionManager"
|
||||||
|
:partManager="partManager"
|
||||||
|
:layerManager="layerManager"
|
||||||
|
:canvasManager="canvasManager"
|
||||||
:toolManager="toolManager"
|
:toolManager="toolManager"
|
||||||
:activeTool="activeTool"
|
:activeTool="activeTool"
|
||||||
/>
|
/>
|
||||||
@@ -1269,6 +1314,7 @@ defineExpose({
|
|||||||
?
|
?
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图层面板组件 -->
|
<!-- 图层面板组件 -->
|
||||||
@@ -1298,9 +1344,11 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- 裁剪图片组件 -->
|
|
||||||
<CropImage ref="cropImageRef" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 裁剪图片组件 -->
|
||||||
|
<CropImage ref="cropImageRef" />
|
||||||
|
<!-- 颜色选择器组件 -->
|
||||||
|
<PalletPanel ref="palletPanelRef" />
|
||||||
|
|
||||||
<!-- <div class="footer-actions">
|
<!-- <div class="footer-actions">
|
||||||
<button class="share-btn">Share</button>
|
<button class="share-btn">Share</button>
|
||||||
@@ -1399,6 +1447,7 @@ defineExpose({
|
|||||||
/* background-color: #f8f8f8; */
|
/* background-color: #f8f8f8; */
|
||||||
:deep(.canvas-container) {
|
:deep(.canvas-container) {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
|
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1411,33 +1460,31 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.background-grid {
|
.background-grid {
|
||||||
--offsetX: 0px;
|
--offsetX: 50%;
|
||||||
--offsetY: 0px;
|
--offsetY: 50%;
|
||||||
--size: 8px;
|
--size: 10px;
|
||||||
--color: #dedcdc;
|
--color: rgba(229, 229,229,0.5);
|
||||||
background-image: -webkit-linear-gradient(
|
background-image: -webkit-linear-gradient(
|
||||||
45deg,
|
90deg,
|
||||||
var(--color) 25%,
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
),
|
),
|
||||||
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent
|
-webkit-linear-gradient(
|
||||||
75%, var(--color) 0);
|
0,
|
||||||
background-image: linear-gradient(
|
var(--color) 1px,
|
||||||
45deg,
|
transparent 0,
|
||||||
var(--color) 25%,
|
);
|
||||||
|
background-image:linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
),
|
),
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
45deg,
|
0,
|
||||||
var(--color) 25%,
|
var(--color) 1px,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 75%,
|
|
||||||
var(--color) 0
|
|
||||||
);
|
);
|
||||||
|
background-color: #fafafa;
|
||||||
background-position: var(--offsetX) var(--offsetY),
|
background-position: var(--offsetX) var(--offsetY),
|
||||||
calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY));
|
calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY));
|
||||||
background-size: calc(var(--size) * 2) calc(var(--size) * 2);
|
background-size: calc(var(--size) * 2) calc(var(--size) * 2);
|
||||||
|
|||||||