ow
This commit is contained in:
1 parent
4adae1bd7d
commit
adcf94df4a
17 files changed
+274
-97
No files matched your search
@@ -1,19 +1,21 @@
|
||||
|
||||
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using CloudBuilder.Core.DatabaseAccessor;
|
||||
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
||||
using CloudBuilder.Core.Extensions;
|
||||
using CloudBuilder.Core.Policy;
|
||||
using CloudBuilder.Core.Service;
|
||||
using CloudBuilder.Master.Data;
|
||||
using CloudBuilder.Master.Entity;
|
||||
using CloudBuilder.Master.Policy;
|
||||
using CloudBuilder.Master.SearchCriteria;
|
||||
using CloudBuilder.Master.Data;
|
||||
using CloudBuilder.Core.Service;
|
||||
using System.Diagnostics;
|
||||
using CloudBuilder.Security.Service;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CloudBuilder.Security.Entity;
|
||||
using CloudBuilder.Security.Utility;
|
||||
using CloudBuilder.Security.Policy;
|
||||
using CloudBuilder.Core.Policy;
|
||||
using CloudBuilder.Security.Service;
|
||||
using CloudBuilder.Security.Utility;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Diagnostics;
|
||||
namespace CloudBuilder.Master.Service
|
||||
{
|
||||
public class PublishPhotoService : IPublishPhotoService
|
||||
@@ -73,13 +75,14 @@ namespace CloudBuilder.Master.Service
|
||||
data.PublishPhoto = publish;
|
||||
|
||||
IRepository<PublishPhotoDetailEntity> repositoryPublishPhotoDetail = repository.GetRepository<IRepository<PublishPhotoDetailEntity>>();
|
||||
data.Details = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publish.PublishName).OrderBy(x => x.OrderBy).ToArray();
|
||||
|
||||
data.Details = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publish.PublishName).OrderBy(x => x.OrderBy).Select(x => new PublishPhotoDetailDisplayData(x)).ToArray();
|
||||
|
||||
bool isAttached = Debugger.IsAttached;
|
||||
if (data.Details != null || data.Details.Length > 0)
|
||||
foreach (var item in data.Details)
|
||||
{
|
||||
item.FormattedPic = PackImageScareFileName(isAttached, item.FileName);
|
||||
item.FileName = PackImageFileName(isAttached, item.FileName);
|
||||
}
|
||||
|
||||
@@ -87,10 +90,20 @@ namespace CloudBuilder.Master.Service
|
||||
return data;
|
||||
}
|
||||
|
||||
private string PackImageFileName(bool isAttached,string fileName)
|
||||
private string PackImageScareFileName(bool isAttached, string fileName)
|
||||
{
|
||||
return (isAttached ? service.Configuration[AppsettingsPolicy.ApplicationUrl] : "")
|
||||
+ fileName?.Replace("\\", "/");
|
||||
var phy = service.Configuration[ServiceSettingsPolicy.PhysicalFileDirectory] ?? "";
|
||||
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||
var scale = (ServiceSettingsPolicy.GetDmsFileScaleDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||
|
||||
return (isAttached ? service.Configuration[AppsettingsPolicy.ApplicationUrl] : "")
|
||||
+ fileName?.Replace("\\", "/").Replace(backup.Replace("\\", "/"), scale.Replace("\\", "/"));
|
||||
}
|
||||
|
||||
private string PackImageFileName(bool isAttached, string fileName)
|
||||
{
|
||||
return (isAttached ? service.Configuration[AppsettingsPolicy.ApplicationUrl] : "")
|
||||
+ fileName?.Replace("\\", "/");
|
||||
}
|
||||
|
||||
[PermissionOperate(name: MasterPermissionPolicy.PublishPhoto, operate: Operate.Find)]
|
||||
@@ -336,9 +349,9 @@ namespace CloudBuilder.Master.Service
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public PublishPhotoDetailEntity[] FindPublishPhotoDetailByPublishName(string publishName)
|
||||
public PublishPhotoDetailDisplayData[] FindPublishPhotoDetailByPublishName(string publishName)
|
||||
{
|
||||
List<PublishPhotoDetailEntity> publishPhotoDetails = new List<PublishPhotoDetailEntity>();
|
||||
List<PublishPhotoDetailDisplayData> publishPhotoDetails = new List<PublishPhotoDetailDisplayData>();
|
||||
if (string.IsNullOrEmpty(publishName)) return publishPhotoDetails.ToArray();
|
||||
|
||||
IRepository<PublishPhotoEntity> repositoryPublishPhoto = repository.GetRepository<IRepository<PublishPhotoEntity>>();
|
||||
@@ -348,7 +361,7 @@ namespace CloudBuilder.Master.Service
|
||||
return publishPhotoDetails.ToArray();
|
||||
|
||||
IRepository<PublishPhotoDetailEntity> repositoryPublishPhotoDetail = repository.GetRepository<IRepository<PublishPhotoDetailEntity>>();
|
||||
PublishPhotoDetailEntity[] dtls = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publishName).OrderBy(x => x.OrderBy).ToArray();
|
||||
PublishPhotoDetailDisplayData[] dtls = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publishName).OrderBy(x => x.OrderBy).Select(x => new PublishPhotoDetailDisplayData(x)).ToArray();
|
||||
|
||||
if (dtls == null || dtls.Length == 0) return publishPhotoDetails.ToArray();
|
||||
|
||||
@@ -356,7 +369,7 @@ namespace CloudBuilder.Master.Service
|
||||
foreach (var item in dtls)
|
||||
{
|
||||
if (item.FileName.ToLower().StartsWith("http")) continue;
|
||||
|
||||
item.FormattedPic = PackImageScareFileName(isAttached, item.FileName);
|
||||
item.FileName = PackImageFileName(isAttached, item.FileName);
|
||||
}
|
||||
|
||||
@@ -364,16 +377,16 @@ namespace CloudBuilder.Master.Service
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public PublishPhotoDetailEntity[] FindPublishPhotoDetail()
|
||||
public PublishPhotoDetailDisplayData[] FindPublishPhotoDetail()
|
||||
{
|
||||
PublishPhotoEntity publishPhoto = repository.DetachedEntities.Where(x => x.OwnerName == service.UserId && x.DefaultIndc == YesNoPolicy.YES).FirstOrDefault();
|
||||
List<PublishPhotoDetailEntity> publishPhotoDetails = new List<PublishPhotoDetailEntity>();
|
||||
List<PublishPhotoDetailDisplayData> publishPhotoDetails = new List<PublishPhotoDetailDisplayData>();
|
||||
if (publishPhoto == null)
|
||||
return publishPhotoDetails.ToArray();
|
||||
|
||||
IRepository<PublishPhotoDetailEntity> repositoryPublishPhotoDetail = repository.GetRepository<IRepository<PublishPhotoDetailEntity>>();
|
||||
|
||||
PublishPhotoDetailEntity[] dtls = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publishPhoto.PublishName).OrderBy(x => x.OrderBy).ToArray();
|
||||
PublishPhotoDetailDisplayData[] dtls = repositoryPublishPhotoDetail.DetachedEntities.Where(x => x.PublishName == publishPhoto.PublishName).OrderBy(x => x.OrderBy).Select(x => new PublishPhotoDetailDisplayData(x)).ToArray();
|
||||
|
||||
if (dtls == null || dtls.Length == 0) return dtls;
|
||||
|
||||
@@ -382,6 +395,7 @@ namespace CloudBuilder.Master.Service
|
||||
{
|
||||
if (item.FileName.ToLower().StartsWith("http")) continue;
|
||||
|
||||
item.FormattedPic = PackImageScareFileName(isAttached, item.FileName);
|
||||
item.FileName = (isAttached ? service.Configuration[AppsettingsPolicy.ApplicationUrl] : "") + item.FileName;
|
||||
}
|
||||
|
||||
@@ -425,7 +439,7 @@ namespace CloudBuilder.Master.Service
|
||||
|
||||
var fileExtension = Path.GetExtension(dmsFile.OrderKey);
|
||||
var filePathRel = Path.Combine(webRootPath.Substring(1), backup, dmsFile.CreatedBy, fileExtension.Replace(".", ""), "");
|
||||
var filePath = '/'+filePathRel.Replace(Path.DirectorySeparatorChar, '/');
|
||||
var filePath = '/' + filePathRel.Replace(Path.DirectorySeparatorChar, '/');
|
||||
string path = Path.Combine(filePath, dmsFile.OrderKey);
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace CloudBuilder.Master.Data
|
||||
{
|
||||
public PublishPhotoEntity? PublishPhoto { get; set; }
|
||||
|
||||
public PublishPhotoDetailEntity[]? Details { get; set; }
|
||||
public PublishPhotoDetailDisplayData[]? Details { get; set; }
|
||||
|
||||
public PublishPhotoFamilyGroupDisplayData[]? FamilyGroups { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using CloudBuilder.Core.Authorization;
|
||||
using CloudBuilder.Master.Entity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudBuilder.Master.Data
|
||||
{
|
||||
[NotMapped]
|
||||
[TypeScript]
|
||||
public class PublishPhotoDetailDisplayData : PublishPhotoDetailEntity
|
||||
{
|
||||
public PublishPhotoDetailDisplayData()
|
||||
{
|
||||
}
|
||||
|
||||
public PublishPhotoDetailDisplayData(PublishPhotoDetailEntity entity)
|
||||
{
|
||||
CopyFrom(entity);
|
||||
}
|
||||
|
||||
public string? FormattedPic { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace CloudBuilder.Master.Service
|
||||
void InsertPublishPhotoDetail(PublishData publishData);
|
||||
void InsertPublishPhotoDetailByFamilyGroup(PublishData publishData);
|
||||
|
||||
PublishPhotoDetailEntity[] FindPublishPhotoDetail();
|
||||
PublishPhotoDetailEntity[] FindPublishPhotoDetailByPublishName(string publishName);
|
||||
PublishPhotoDetailDisplayData[] FindPublishPhotoDetail();
|
||||
PublishPhotoDetailDisplayData[] FindPublishPhotoDetailByPublishName(string publishName);
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,13 @@ return Task.Run(() => serviceRequest.PostAsJsonAsync<PublishPhotoData,PublishP
|
||||
{
|
||||
Task.Run(() => serviceRequest.PostAsync("/PublishPhoto/Delete?key=" + key.ToString())).GetAwaiter().GetResult(); }
|
||||
|
||||
public PublishPhotoDetailEntity[] FindPublishPhotoDetailByPublishName(string publishName)
|
||||
public PublishPhotoDetailDisplayData[] FindPublishPhotoDetailByPublishName(string publishName)
|
||||
{
|
||||
return Task.Run(() => serviceRequest.PostAsJsonAsync<PublishPhotoDetailEntity[]>("/PublishPhoto/FindPublishPhotoDetailByPublishName?publishName=" + publishName.ToString())).GetAwaiter().GetResult(); }
|
||||
return Task.Run(() => serviceRequest.PostAsJsonAsync<PublishPhotoDetailDisplayData[]>("/PublishPhoto/FindPublishPhotoDetailByPublishName?publishName=" + publishName.ToString())).GetAwaiter().GetResult(); }
|
||||
|
||||
public PublishPhotoDetailEntity[] FindPublishPhotoDetail()
|
||||
public PublishPhotoDetailDisplayData[] FindPublishPhotoDetail()
|
||||
{
|
||||
return Task.Run(() => serviceRequest.PostAsJsonAsync<PublishPhotoDetailEntity[]> ("/PublishPhoto/FindPublishPhotoDetail")).GetAwaiter().GetResult(); }
|
||||
return Task.Run(() => serviceRequest.PostAsJsonAsync<PublishPhotoDetailDisplayData[]> ("/PublishPhoto/FindPublishPhotoDetail")).GetAwaiter().GetResult(); }
|
||||
|
||||
public void InsertPublishPhotoDetail(PublishData publishData)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<NameOfLastUsedPublishProfile>D:\Docker\CloudBuilder\CloudBuilder.Web.Entry\Properties\PublishProfiles\FolderProfile4.pubxml</NameOfLastUsedPublishProfile>
|
||||
<NameOfLastUsedPublishProfile>D:\Docker\CloudBuilder\CloudBuilder.Web.Entry\Properties\PublishProfiles\FolderProfile3.pubxml</NameOfLastUsedPublishProfile>
|
||||
<ActiveDebugProfile>http</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<_PublishTargetUrl>C:\inetpub\wwwroot\dockwebapi2</_PublishTargetUrl>
|
||||
<History>True|2026-09-22T03:19:19.6087109Z||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
|
||||
<History>True|2026-09-22T15:03:13.6509491Z||;True|2026-09-22T16:07:15.9177123+08:00||;True|2026-09-22T11:19:19.6087109+08:00||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<_PublishTargetUrl>C:\inetpub\wwwroot\windows</_PublishTargetUrl>
|
||||
<History>True|2026-09-22T07:18:53.4935889Z||;True|2026-09-22T11:24:43.8593406+08:00||;True|2026-09-19T09:55:09.0653660+08:00||;True|2026-09-19T09:53:13.8933944+08:00||;</History>
|
||||
<History>True|2026-09-22T15:02:08.0902556Z||;True|2026-09-22T16:47:49.2530282+08:00||;True|2026-09-22T15:18:53.4935889+08:00||;True|2026-09-22T11:24:43.8593406+08:00||;True|2026-09-19T09:55:09.0653660+08:00||;True|2026-09-19T09:53:13.8933944+08:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
+20
-3
@@ -1,10 +1,27 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/youjia.svg" />
|
||||
<!--
|
||||
这里的信息决定「浏览器标签页」和「微信收藏 / 转发卡片」长什么样:
|
||||
卡片标题取 document.title(本文的 <title>,之后由 src/App.vue 按路由 meta 覆盖),
|
||||
卡片副标题取 description,图标取 icon(微信不认 svg,所以用 png 的 logo)。
|
||||
微信会缓存卡片信息:换过文案/图标后把下面的 ?v= 递增一位,促使它重新抓取。
|
||||
-->
|
||||
<link rel="icon" type="image/png" href="/images/logo.png?v=2" />
|
||||
<link rel="shortcut icon" href="/images/logo.png?v=2" />
|
||||
<link rel="apple-touch-icon" href="/images/logo.png?v=2" />
|
||||
<!-- 手机把本站「添加到主屏幕」时的名称 -->
|
||||
<meta name="apple-mobile-web-app-title" content="有家相册" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title></title>
|
||||
<title>有家相册</title>
|
||||
<meta name="description" content="欢迎回家,记录温暖瞬间" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="有家相册" />
|
||||
<meta property="og:title" content="有家相册" />
|
||||
<meta property="og:description" content="欢迎回家,记录温暖瞬间" />
|
||||
<!-- 转发到聊天的大图卡片用它:512×512(微信要求不小于 300×300),由品牌 svg 矢量渲染而来 -->
|
||||
<meta property="og:image" content="/images/logo-512.png?v=2" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div class="psp">
|
||||
<!-- 图片舞台 -->
|
||||
<div class="psp-stage" ref="stageEl" :class="{ 'is-paused': !isPlaying && !!current }"
|
||||
<div class="psp-stage" ref="stageEl"
|
||||
:class="{ 'is-paused': !isPlaying && !!current, 'is-immersive': !controlsVisible }"
|
||||
:style="{ '--psp-kb': interval + 0.8 + 's', '--psp-out-span': outPlan.span + 's' }"
|
||||
:title="isPlaying ? '单击暂停' : '单击看大图'" @click="onStageClick">
|
||||
:title="stageTitle" @click="onStageClick">
|
||||
<!-- 组合类:多图同台 + 相框 / 气泡 / 花瓣 / 彩纸 / 聚光 / 灯柱 / 舞台 / 荧光棒(演唱会等 5 套) -->
|
||||
<!-- out-in:上一屏彻底走完才让下一屏进场,两屏绝不重叠(逐张离场也等最后一张飞完) -->
|
||||
<transition v-if="isShowcase" :name="'psp-fx-' + fxName" :duration="fxDuration" mode="out-in">
|
||||
@@ -71,12 +72,25 @@
|
||||
<PictureOutlined />
|
||||
<p>暂无图片</p>
|
||||
</div>
|
||||
|
||||
<!-- 控制区收起后画面上留一个恢复入口:整页用法没有弹窗右上角按钮,
|
||||
否则收起后就再也点不回来了 -->
|
||||
<button v-if="!controlsVisible" class="psp-restore" type="button" title="显示控制区"
|
||||
@click.stop="toggleControls">
|
||||
<EyeOutlined />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 控制条 -->
|
||||
<footer class="psp-bar">
|
||||
<!-- 控制条(收起后舞台吃掉这部分空间,图片最大化) -->
|
||||
<footer v-if="controlsVisible" class="psp-bar">
|
||||
<!-- 功能图标行 -->
|
||||
<div class="psp-actions">
|
||||
<!-- 隐藏/显示控制区:整页用法时父组件会顺带藏掉 layout 顶栏,做到整页全屏 -->
|
||||
<button class="psp-act" type="button" :class="{ 'is-on': !controlsVisible }"
|
||||
:title="controlsVisible ? '隐藏控制区' : '显示控制区'" @click.stop="toggleControls">
|
||||
<EyeInvisibleOutlined v-if="controlsVisible" />
|
||||
<EyeOutlined v-else />
|
||||
</button>
|
||||
<button class="psp-act" type="button" title="下载当前图片" @click.stop="onDownload">
|
||||
<DownloadOutlined />
|
||||
<span class="psp-badge" :class="{ 'is-on': downloaded }">{{ downloaded ? "" : "" }}</span>
|
||||
@@ -177,8 +191,9 @@
|
||||
</transition>
|
||||
|
||||
<!-- 精看:暂停时单击图片放大看。复用 ant-design 图片预览(自带缩放 / 旋转 / 翻转),
|
||||
图片本体藏起来,预览层由组件自己渲染,不受舞台裁切影响 -->
|
||||
<AImage :src="current?.url || ''" :style="{ display: 'none' }"
|
||||
图片本体藏起来,预览层由组件自己渲染,不受舞台裁切影响。
|
||||
这里用原图(full),舞台轮播放的是缩略图,精看要看清楚 -->
|
||||
<AImage :src="currentFull" :style="{ display: 'none' }"
|
||||
:preview="{ visible: picVisible, onVisibleChange: setPicVisible }" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -187,20 +202,24 @@
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { Image as AImage, message } from "ant-design-vue";
|
||||
import { request } from "@/utils/request";
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import {
|
||||
DownOutlined, DownloadOutlined, CloseOutlined, PictureOutlined, EllipsisOutlined,
|
||||
ThunderboltOutlined, RetweetOutlined, SwapOutlined, StepBackwardFilled,
|
||||
StepForwardFilled, CaretRightFilled, PauseOutlined, UnorderedListOutlined,
|
||||
EyeOutlined, EyeInvisibleOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
|
||||
/** 一张图片 */
|
||||
export interface PhotoSlideItem {
|
||||
/** 原图地址 */
|
||||
/** 展示地址(舞台幻灯片 + 列表缩略图):一般给缩略图,轮播加载更快 */
|
||||
url: string;
|
||||
/** 缩略图地址(列表用,缺省用原图) */
|
||||
/** 缩略图地址(列表用,缺省用 url) */
|
||||
thumb?: string;
|
||||
/** 名称(下载文件名) */
|
||||
name?: string;
|
||||
/** 原图地址:精看与下载用它,缺省退回 url */
|
||||
full?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -208,10 +227,15 @@ const props = withDefaults(
|
||||
items?: PhotoSlideItem[];
|
||||
/** 每张停留秒数 */
|
||||
interval?: number;
|
||||
/** 底部控制区是否显示(页面可用它做「整页全屏」) */
|
||||
controls?: boolean;
|
||||
}>(),
|
||||
{ items: () => [], interval: 5 }
|
||||
{ items: () => [], interval: 5, controls: true }
|
||||
);
|
||||
const emit = defineEmits<{ (e: "close"): void }>();
|
||||
const emit = defineEmits<{
|
||||
(e: "close"): void;
|
||||
(e: "update:controls", visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* ---------------- 基础状态 ---------------- */
|
||||
const items = computed(() => props.items || []);
|
||||
@@ -225,12 +249,30 @@ const showList = ref(false);
|
||||
const menu = ref<"" | "effect" | "more">("");
|
||||
const downloaded = ref(false);
|
||||
|
||||
/**
|
||||
* 底部控制区是否显示:由功能图标行的眼睛图标切换。
|
||||
* 收起后舞台吃掉这部分空间,图片最大化;父组件可用 v-model:controls 拿到这个状态
|
||||
* 去做整页全屏(例如 Preview.vue 藏掉 layout 顶部的两条栏)。
|
||||
*
|
||||
* passive: true 是关键:不传的话 useVModel 直接读写 props,
|
||||
* 父组件没绑 v-model:controls 时内部改了值只会 emit 出去、props 不变,
|
||||
* 按钮就"点了没反应"(与 VideoPlayer 一致)。
|
||||
*/
|
||||
const controlsVisible = useVModel(props, "controls", emit, { passive: true });
|
||||
|
||||
const toggleControls = () => {
|
||||
menu.value = ""; // 顺手收起可能打开的弹层(特效 / 更多)
|
||||
controlsVisible.value = !controlsVisible.value;
|
||||
};
|
||||
|
||||
/** 舞台元素与像素尺寸:圆形格子的直径按舞台短边换算,避免非正方舞台被压成椭圆 */
|
||||
const stageEl = ref<HTMLElement | null>(null);
|
||||
const stageSize = ref({ w: 0, h: 0 });
|
||||
let stageRo: ResizeObserver | null = null;
|
||||
|
||||
const current = computed<PhotoSlideItem | null>(() => items.value[index.value] ?? null);
|
||||
/** 精看与下载用原图(full),没单独给就退回展示地址 */
|
||||
const currentFull = computed(() => current.value?.full || current.value?.url || "");
|
||||
const modeTitle = computed(
|
||||
() => ({ list: "顺序循环", random: "随机播放", once: "播完停止" }[mode.value])
|
||||
);
|
||||
@@ -789,15 +831,30 @@ const picVisible = ref(false);
|
||||
const setPicVisible = (v: boolean) => {
|
||||
picVisible.value = v;
|
||||
};
|
||||
/** 单击舞台:播放中先暂停(原来的行为);已经暂停则打开精看,效果同 DmsFileEnquiry 的单图精看 */
|
||||
/**
|
||||
* 单击舞台:播放中先暂停(原来的行为);已经暂停则打开精看,效果同 DmsFileEnquiry 的单图精看。
|
||||
*
|
||||
* 页内全屏(控制区已收起)时只切暂停/播放:那会儿没有控制条,
|
||||
* 再叠一层大图预览会盖住整屏、也没有恢复入口,容易把人"困住"。
|
||||
*/
|
||||
const onStageClick = () => {
|
||||
if (!items.value.length) return;
|
||||
if (!controlsVisible.value) {
|
||||
toggle();
|
||||
return;
|
||||
}
|
||||
if (isPlaying.value) {
|
||||
toggle();
|
||||
return;
|
||||
}
|
||||
setPicVisible(true);
|
||||
};
|
||||
|
||||
/** 舞台提示:页内全屏时没有"看大图",文案跟着变,免得点了跟说好的不一样 */
|
||||
const stageTitle = computed(() => {
|
||||
if (!controlsVisible.value) return isPlaying.value ? "单击暂停" : "单击播放";
|
||||
return isPlaying.value ? "单击暂停" : "单击看大图";
|
||||
});
|
||||
const playAt = (i: number) => {
|
||||
index.value = i;
|
||||
elapsed.value = 0;
|
||||
@@ -848,13 +905,15 @@ const onDownload = async () => {
|
||||
if (!it || downloading.value) return;
|
||||
downloading.value = true;
|
||||
try {
|
||||
// 下载要原图,不是舞台上那张缩略图
|
||||
const url = currentFull.value;
|
||||
const res = await request<ArrayBuffer>({
|
||||
url: it.url,
|
||||
url,
|
||||
method: "get",
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const ext = (it.url.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1] || "jpg";
|
||||
const ext = (url.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1] || "jpg";
|
||||
const a = document.createElement("a");
|
||||
a.href = blobUrl;
|
||||
a.download = `${it.name || "image"}.${ext}`;
|
||||
@@ -963,11 +1022,37 @@ onBeforeUnmount(() => {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 暂停后单击就是精看,光标跟着变 */
|
||||
.psp-stage.is-paused {
|
||||
/* 暂停后单击就是精看,光标跟着变;
|
||||
页内全屏(.is-immersive)时单击是继续播放,不给放大镜光标,免得误导 */
|
||||
.psp-stage.is-paused:not(.is-immersive) {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
/* 控制区收起后的恢复入口(整页用法没有弹窗右上角按钮) */
|
||||
.psp-restore {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, .38);
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
opacity: .55;
|
||||
transition: opacity .2s;
|
||||
}
|
||||
|
||||
.psp-restore:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.psp-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
@@ -35,9 +35,23 @@ async function setupApp() {
|
||||
// 初始化 i18n
|
||||
await setupI18n(app);
|
||||
|
||||
setupShareImage();
|
||||
|
||||
app.mount("#app");
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享卡片(微信收藏/转发等)的缩略图要求绝对地址,相对路径会被忽略,
|
||||
* 这里按当前域名把 index.html 里的 og:image 补全。
|
||||
*/
|
||||
function setupShareImage() {
|
||||
const meta = document.querySelector('meta[property="og:image"]');
|
||||
const content = meta?.getAttribute("content");
|
||||
if (meta && content && !/^https?:\/\//i.test(content)) {
|
||||
meta.setAttribute("content", new URL(content, location.origin).href);
|
||||
}
|
||||
}
|
||||
|
||||
setupPlugins();
|
||||
|
||||
setupApp();
|
||||
@@ -110,7 +110,9 @@ export async function setupRouter(app: App) {
|
||||
),
|
||||
props: true,
|
||||
meta: {
|
||||
title: "首页",
|
||||
// 这个 title 会被 App.vue 写进 document.title,
|
||||
// 微信「收藏 / 分享」卡片的第一行取的就是它,所以用品牌名而不是"首页"
|
||||
title: "有家相册",
|
||||
},
|
||||
});
|
||||
router.addRoute({
|
||||
@@ -129,17 +131,19 @@ export async function setupRouter(app: App) {
|
||||
name: "dashboard",
|
||||
component: () => import("../layout/home.vue"),
|
||||
meta: {
|
||||
title: "首页",
|
||||
// 这里不要用 description:App.vue 会优先把 meta.description 写进 document.title,
|
||||
// 而微信「收藏 / 转发」卡片的第一行取的就是 document.title,必须是品牌名,
|
||||
// 否则分享出去显示的是"欢迎页"
|
||||
title: "有家相册",
|
||||
role: "*",
|
||||
description: "欢迎页",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "dashboardWelcome",
|
||||
meta: {
|
||||
title: "有家相册",
|
||||
role: "*",
|
||||
description: "欢迎页",
|
||||
},
|
||||
component: () => import("../views/dashboard.vue"),
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { request,download } from "@/utils/request";
|
||||
import type { PublishPhotoData } from "@/data/auto/publishPhotoData";
|
||||
import type { PublishPhotoSearchCriteria } from "@/data/auto/publishPhotoSearchCriteria";
|
||||
import type { PublishPhotoResultData } from "@/data/auto/publishPhotoResultData";
|
||||
import type { PublishPhotoDetailEntity } from "@/data/auto/publishPhotoDetailEntity";
|
||||
import type { PublishPhotoDetailDisplayData } from "@/data/auto/publishPhotoDetailDisplayData";
|
||||
import type { PublishData } from "@/data/auto/publishData";
|
||||
|
||||
class PublishPhotoService{
|
||||
@@ -70,7 +70,7 @@ class PublishPhotoService{
|
||||
};
|
||||
|
||||
async FindPublishPhotoDetailByPublishName(data: string) {
|
||||
return request<PublishPhotoDetailEntity[]>(
|
||||
return request<PublishPhotoDetailDisplayData[]>(
|
||||
{
|
||||
url: "/PublishPhoto/FindPublishPhotoDetailByPublishName?publishName="+data,
|
||||
method: "post",
|
||||
@@ -80,7 +80,7 @@ class PublishPhotoService{
|
||||
};
|
||||
|
||||
async FindPublishPhotoDetail() {
|
||||
return request<PublishPhotoDetailEntity[]>(
|
||||
return request<PublishPhotoDetailDisplayData[]>(
|
||||
{
|
||||
url: "/PublishPhoto/FindPublishPhotoDetail",
|
||||
method: "post",
|
||||
|
||||
@@ -23,4 +23,27 @@ select[readonly]:not([disabled])+.dropdown-toggle.custom-selectpicker,
|
||||
input[type=checkbox][readonly]:not([disabled]),
|
||||
input[type=radio][readonly]:not([disabled]) {
|
||||
background-color: #f3f3f3 !important;
|
||||
}
|
||||
|
||||
/* 页面级「整页全屏」(沉浸看片/看图):
|
||||
body 上有 .pv-fullscreen 时才生效,该 class 由影视厅 PreviewVideo.vue、
|
||||
图片预览 Preview.vue 在收起控制区时临时挂上,离开页面立即移除,所以不会影响其它页面。
|
||||
1. 藏掉 layout 顶部两条栏(标题栏 + 页面头);
|
||||
2. 内容区与页面容器撑到整屏,去掉原有的 8px 内边距,做到真正的全屏铺满。
|
||||
容器高度是行内样式(bsInfo.height - 120)、Preview 还用了负外边距,只能用 !important 覆盖。 */
|
||||
body.pv-fullscreen .layout-header,
|
||||
body.pv-fullscreen .layout-page-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.pv-fullscreen .layout-content-view {
|
||||
height: 100vh !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
body.pv-fullscreen .pv-page {
|
||||
width: 100% !important;
|
||||
height: 100vh !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="pv-page">
|
||||
<PhotoSlidePlayer :items="slides" @close="onClose" />
|
||||
<PhotoSlidePlayer v-model:controls="controlsVisible" :items="slides" @close="onClose" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { watch, computed, inject, reactive, ref, VNode, h, unref, onMounted, onBeforeMount } from "vue";
|
||||
import { watch, computed, inject, reactive, ref, VNode, h, unref, onMounted, onBeforeMount, onUnmounted } from "vue";
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import PhotoSlidePlayer from "@/components/basic/PhotoSlidePlayer.vue";
|
||||
import type { PhotoSlideItem } from "@/components/basic/PhotoSlidePlayer.vue";
|
||||
import { PublishPhotoDetailEntity } from "@/data/auto/publishPhotoDetailEntity";
|
||||
import { PublishPhotoDetailDisplayData } from "@/data/auto/publishPhotoDetailDisplayData";
|
||||
import publishPhotoService from "@/service/master/publishPhotoService";
|
||||
import { bsInfo } from "@/utils/browser"
|
||||
import { DmsFileSearchCriteria } from "@/data/auto/dmsFileSearchCriteria";
|
||||
@@ -20,7 +20,13 @@ import dmsFileService from "@/service/master/dmsFileService";
|
||||
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { SearchCriteriaResultData } from "@/data/auto/searchCriteriaResultData";
|
||||
|
||||
const images = ref<PublishPhotoDetailEntity[]>([]);
|
||||
/**
|
||||
* 后端 PublishPhotoService 会把同一张图给出两个地址(见 PublishPhotoService.auto.cs):
|
||||
* FileName —— 原图
|
||||
* FormattedPic —— 缩放后的缩略图
|
||||
* 幻灯片轮播与图片列表用缩略图(加载快),精看和下载用原图。
|
||||
*/
|
||||
const images = ref<PublishPhotoDetailDisplayData[]>([]);
|
||||
|
||||
|
||||
const route = useRoute();
|
||||
@@ -30,6 +36,22 @@ const router = useRouter();
|
||||
const publishName = ref<string>(route.params.id as string)
|
||||
const mode = ref<string>(route.params.mode as string)
|
||||
|
||||
/**
|
||||
* 控制区显隐:与影视厅 PreviewVideo.vue 同一套机制——
|
||||
* 收起控制区时给 body 挂 .pv-fullscreen,藏掉 layout 顶部的两条栏
|
||||
* (标题栏 + 页面头)并把内容区撑到整屏,图片真正占满全屏。
|
||||
* class 挂在 body 上,离开本页立即移除,不会影响其它页面(样式见 styles/main.css)。
|
||||
*/
|
||||
const controlsVisible = ref(true);
|
||||
|
||||
watch(controlsVisible, (visible) => {
|
||||
document.body.classList.toggle("pv-fullscreen", !visible);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.classList.remove("pv-fullscreen");
|
||||
});
|
||||
|
||||
const toIndex = ref<number>(0)
|
||||
const beforeChange = (from: number, to: number) => {
|
||||
if (to > 0) {
|
||||
@@ -37,11 +59,12 @@ const beforeChange = (from: number, to: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 交给 PhotoSlidePlayer 播放:FileName 为缩略图,PublishName 为原图
|
||||
// 交给 PhotoSlidePlayer 播放:舞台与列表用 FormattedPic(缩略图),精看/下载用 FileName(原图)
|
||||
const slides = computed<PhotoSlideItem[]>(() =>
|
||||
images.value.map((item) => ({
|
||||
url: item.PublishName,
|
||||
thumb: item.FileName,
|
||||
url: item.FormattedPic || item.FileName,
|
||||
thumb: item.FormattedPic || item.FileName,
|
||||
full: item.FileName || item.FormattedPic,
|
||||
name: (item.FileName || "").replace(/\.[^./\\]+$/, "") || undefined,
|
||||
}))
|
||||
);
|
||||
@@ -69,17 +92,11 @@ onMounted(async () => {
|
||||
}
|
||||
else if (publishName.value && publishName.value.length > 0) {
|
||||
publishPhotoService.FindPublishPhotoDetailByPublishName(publishName.value).then(res => {
|
||||
for (let i = 0; i <res.length; i++) {
|
||||
res[i].PublishName = res[i].FileName;
|
||||
}
|
||||
images.value = res;
|
||||
});
|
||||
}
|
||||
else {
|
||||
publishPhotoService.FindPublishPhotoDetail().then(res => {
|
||||
for (let i = 0; i <res.length; i++) {
|
||||
res[i].PublishName = res[i].FileName;
|
||||
}
|
||||
images.value = res;
|
||||
});
|
||||
}
|
||||
@@ -110,12 +127,12 @@ const findData = async (params: number) => {
|
||||
if (dr && dr.Datas && dr.Datas.length > 0) {
|
||||
|
||||
images.value = [];
|
||||
let detail: PublishPhotoDetailEntity;
|
||||
let detail: PublishPhotoDetailDisplayData;
|
||||
for (let i = 0; i < dr.Datas.length; i++) {
|
||||
detail = new PublishPhotoDetailEntity()
|
||||
detail.FileName = dr.Datas[i].FormattedPic;
|
||||
detail = new PublishPhotoDetailDisplayData()
|
||||
detail.FileName = dr.Datas[i].Url; // 原图:精看 / 下载
|
||||
detail.FormattedPic = dr.Datas[i].FormattedPic; // 缩略图:舞台轮播 / 列表
|
||||
detail.Notice = dr.Datas[i].LandscapeIndc;
|
||||
detail.PublishName = dr.Datas[i].Url;
|
||||
images.value.push(detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,27 +130,3 @@ const loadVideos = async () => {
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 注意:这里是全局样式,但所有选择器都要求 body 上有 .pv-fullscreen
|
||||
(只在影视厅收起控制区时由 PreviewVideo.vue 临时挂上,离开页面即移除),
|
||||
所以不会影响其它页面。
|
||||
|
||||
收起控制区 = 页面全屏:
|
||||
1. 藏掉 layout 顶部两条栏(标题栏 64px + 页面头 42px);
|
||||
2. 内容区和视频容器撑到整屏,去掉原有的 8px 内边距,做到真正的全屏铺满。
|
||||
容器高度是行内样式(bsInfo.height - 100/-120),只能用 !important 覆盖。 */
|
||||
body.pv-fullscreen .layout-header,
|
||||
body.pv-fullscreen .layout-page-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.pv-fullscreen .layout-content-view {
|
||||
height: 100vh !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
body.pv-fullscreen .pv-page {
|
||||
height: 100vh !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user