reference-image-manager.component.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. interface ReferenceImage {
  5. id: string;
  6. name: string;
  7. url: string;
  8. categories: string[];
  9. description?: string;
  10. tags?: string[];
  11. uploadDate: Date;
  12. }
  13. interface SelectedFile {
  14. file: File;
  15. name: string;
  16. size: number;
  17. preview: string;
  18. }
  19. interface AIQuestion {
  20. id: string;
  21. title: string;
  22. description: string;
  23. example: string;
  24. icon: string;
  25. category?: string;
  26. }
  27. @Component({
  28. selector: 'app-reference-image-manager',
  29. standalone: true,
  30. imports: [CommonModule, FormsModule],
  31. templateUrl: './reference-image-manager.component.html',
  32. styleUrls: ['./reference-image-manager.component.scss']
  33. })
  34. export class ReferenceImageManagerComponent implements OnInit {
  35. @Input() projectId: string = '';
  36. @Output() imagesUpdated = new EventEmitter<ReferenceImage[]>();
  37. // 视图状态
  38. viewMode: 'grid' | 'list' = 'grid';
  39. activeFilter: string = 'all';
  40. selectedImageId: string | null = null;
  41. // 数据
  42. referenceImages: ReferenceImage[] = [];
  43. filteredImages: ReferenceImage[] = [];
  44. // 对话框状态
  45. showUploadDialog = false;
  46. showEditDialog = false;
  47. showAIDialog = false;
  48. showPreviewDialog = false;
  49. // 上传相关
  50. isDragging = false;
  51. selectedFiles: SelectedFile[] = [];
  52. // 编辑相关
  53. editingImage: ReferenceImage | null = null;
  54. newTag = '';
  55. // AI助手相关
  56. aiImage: ReferenceImage | null = null;
  57. customQuestion = '';
  58. // AI建议追问(参考图多的情况)
  59. aiQuestionsMultiple: AIQuestion[] = [
  60. {
  61. id: 'q1',
  62. title: '明确灯光参考',
  63. description: '当有多张参考图时,需要明确哪张图主要用于灯光布局参考',
  64. example: '请问这几张参考图中,哪一张是您想要的灯光效果?主要参考它的主光源布置还是氛围灯效果?',
  65. icon: 'icon-lightbulb',
  66. category: 'lighting'
  67. },
  68. {
  69. id: 'q2',
  70. title: '区分软装参考',
  71. description: '识别哪些图片用于软装家具的参考',
  72. example: '这些参考图中,哪几张是用于软装搭配的?具体是参考家具的款式、颜色还是整体搭配方案?',
  73. icon: 'icon-couch',
  74. category: 'furniture'
  75. },
  76. {
  77. id: 'q3',
  78. title: '确认氛围感觉',
  79. description: '明确整体氛围和感觉的主要参考图',
  80. example: '您希望达到的整体氛围感觉,主要是参考哪张图?是温馨、现代还是其他风格?',
  81. icon: 'icon-stars',
  82. category: 'atmosphere'
  83. },
  84. {
  85. id: 'q4',
  86. title: '结构布局说明',
  87. description: '确认空间结构和布局的参考依据',
  88. example: '空间的布局结构,是按照哪张图来的?需要完全一致还是可以适当调整?',
  89. icon: 'icon-cube',
  90. category: 'structure'
  91. }
  92. ];
  93. // AI建议追问(参考图少的情况)
  94. aiQuestionsFew: AIQuestion[] = [
  95. {
  96. id: 'q5',
  97. title: '细化感觉来源',
  98. description: '深入了解客户喜欢这张图的具体原因',
  99. example: '您喜欢这张图的哪些方面?是整体的色调、光线效果、还是家具的搭配?能具体说说吗?',
  100. icon: 'icon-heart',
  101. },
  102. {
  103. id: 'q6',
  104. title: '明确重点元素',
  105. description: '确认图片中最重要的参考元素',
  106. example: '这张图里,您最想要的是什么?比如墙面的颜色、地板材质、还是家具的款式?请按重要性排个序。',
  107. icon: 'icon-target',
  108. },
  109. {
  110. id: 'q7',
  111. title: '探索细节要求',
  112. description: '挖掘客户对细节的具体期望',
  113. example: '关于这张图的细节,比如灯具的样式、窗帘的材质、墙面的装饰等,您有特别的偏好吗?',
  114. icon: 'icon-zoom-in',
  115. },
  116. {
  117. id: 'q8',
  118. title: '确认可调整空间',
  119. description: '了解哪些地方可以灵活调整',
  120. example: '如果完全按这张图来可能有些困难,哪些元素是必须保留的?哪些可以根据实际情况调整?',
  121. icon: 'icon-sliders',
  122. }
  123. ];
  124. previewImageUrl = '';
  125. ngOnInit() {
  126. this.loadReferenceImages();
  127. }
  128. // 加载参考图数据
  129. loadReferenceImages() {
  130. // 模拟数据,实际应该从服务器加载
  131. this.referenceImages = [
  132. {
  133. id: '1',
  134. name: '客厅灯光参考.jpg',
  135. url: document.baseURI+'/assets/images/portfolio-1.svg',
  136. categories: ['lighting', 'atmosphere'],
  137. description: '参考主灯和氛围灯的布局,营造温馨的晚间氛围',
  138. tags: ['吊灯', '暖光', '氛围灯'],
  139. uploadDate: new Date('2024-01-15')
  140. },
  141. {
  142. id: '2',
  143. name: '软装搭配方案.jpg',
  144. url: document.baseURI+'/assets/images/portfolio-2.svg',
  145. categories: ['furniture'],
  146. description: '参考沙发、茶几的款式和颜色搭配',
  147. tags: ['现代简约', '布艺沙发', '木质茶几'],
  148. uploadDate: new Date('2024-01-16')
  149. },
  150. {
  151. id: '3',
  152. name: '整体效果图.jpg',
  153. url: document.baseURI+'/assets/images/portfolio-3.svg',
  154. categories: [],
  155. description: '',
  156. tags: [],
  157. uploadDate: new Date('2024-01-17')
  158. }
  159. ];
  160. this.filteredImages = [...this.referenceImages];
  161. }
  162. // 分类筛选
  163. filterByCategory(category: string) {
  164. this.activeFilter = category;
  165. if (category === 'all') {
  166. this.filteredImages = [...this.referenceImages];
  167. } else if (category === 'uncategorized') {
  168. this.filteredImages = this.referenceImages.filter(img =>
  169. !img.categories || img.categories.length === 0
  170. );
  171. } else {
  172. this.filteredImages = this.referenceImages.filter(img =>
  173. img.categories && img.categories.includes(category)
  174. );
  175. }
  176. }
  177. // 获取分类数量
  178. getCategoryCount(category: string): number {
  179. return this.referenceImages.filter(img =>
  180. img.categories && img.categories.includes(category)
  181. ).length;
  182. }
  183. // 获取未分类数量
  184. getUncategorizedCount(): number {
  185. return this.referenceImages.filter(img =>
  186. !img.categories || img.categories.length === 0
  187. ).length;
  188. }
  189. // 获取分类标签文本
  190. getCategoryLabel(category: string): string {
  191. const labels: { [key: string]: string } = {
  192. 'lighting': '灯光',
  193. 'furniture': '软装',
  194. 'atmosphere': '氛围',
  195. 'structure': '结构'
  196. };
  197. return labels[category] || category;
  198. }
  199. // 获取筛选器标签
  200. getFilterLabel(): string {
  201. const labels: { [key: string]: string } = {
  202. 'all': '全部',
  203. 'lighting': '灯光参考',
  204. 'furniture': '软装参考',
  205. 'atmosphere': '氛围参考',
  206. 'structure': '结构参考',
  207. 'uncategorized': '未分类'
  208. };
  209. return labels[this.activeFilter] || '';
  210. }
  211. // 选择图片
  212. selectImage(image: ReferenceImage) {
  213. this.selectedImageId = image.id;
  214. }
  215. // 打开上传对话框
  216. openUploadDialog() {
  217. this.showUploadDialog = true;
  218. this.selectedFiles = [];
  219. }
  220. // 关闭上传对话框
  221. closeUploadDialog() {
  222. this.showUploadDialog = false;
  223. this.selectedFiles = [];
  224. }
  225. // 文件拖拽
  226. onDragOver(event: DragEvent) {
  227. event.preventDefault();
  228. this.isDragging = true;
  229. }
  230. onDragLeave(event: DragEvent) {
  231. event.preventDefault();
  232. this.isDragging = false;
  233. }
  234. onDrop(event: DragEvent) {
  235. event.preventDefault();
  236. this.isDragging = false;
  237. const files = event.dataTransfer?.files;
  238. if (files) {
  239. this.processFiles(files);
  240. }
  241. }
  242. // 文件选择
  243. onFileSelected(event: Event) {
  244. const input = event.target as HTMLInputElement;
  245. if (input.files) {
  246. this.processFiles(input.files);
  247. }
  248. }
  249. // 处理文件
  250. processFiles(files: FileList) {
  251. for (let i = 0; i < files.length; i++) {
  252. const file = files[i];
  253. if (file.type.startsWith('image/')) {
  254. const reader = new FileReader();
  255. reader.onload = (e) => {
  256. this.selectedFiles.push({
  257. file: file,
  258. name: file.name,
  259. size: file.size,
  260. preview: e.target?.result as string
  261. });
  262. };
  263. reader.readAsDataURL(file);
  264. }
  265. }
  266. }
  267. // 移除选中的文件
  268. removeSelectedFile(index: number) {
  269. this.selectedFiles.splice(index, 1);
  270. }
  271. // 格式化文件大小
  272. formatFileSize(bytes: number): string {
  273. if (bytes < 1024) return bytes + ' B';
  274. if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
  275. return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
  276. }
  277. // 上传文件
  278. uploadFiles() {
  279. // 模拟上传
  280. this.selectedFiles.forEach(file => {
  281. const newImage: ReferenceImage = {
  282. id: Date.now().toString() + Math.random(),
  283. name: file.name,
  284. url: file.preview,
  285. categories: [],
  286. description: '',
  287. tags: [],
  288. uploadDate: new Date()
  289. };
  290. this.referenceImages.unshift(newImage);
  291. });
  292. this.filteredImages = [...this.referenceImages];
  293. this.imagesUpdated.emit(this.referenceImages);
  294. this.closeUploadDialog();
  295. }
  296. // 编辑图片
  297. editImage(image: ReferenceImage, event: Event) {
  298. event.stopPropagation();
  299. this.editingImage = { ...image };
  300. this.showEditDialog = true;
  301. }
  302. // 关闭编辑对话框
  303. closeEditDialog() {
  304. this.showEditDialog = false;
  305. this.editingImage = null;
  306. }
  307. // 判断分类是否选中
  308. isCategorySelected(category: string): boolean {
  309. return this.editingImage?.categories?.includes(category) || false;
  310. }
  311. // 切换分类
  312. toggleCategory(category: string) {
  313. if (!this.editingImage) return;
  314. if (!this.editingImage.categories) {
  315. this.editingImage.categories = [];
  316. }
  317. const index = this.editingImage.categories.indexOf(category);
  318. if (index > -1) {
  319. this.editingImage.categories.splice(index, 1);
  320. } else {
  321. this.editingImage.categories.push(category);
  322. }
  323. }
  324. // 添加标签
  325. addTag() {
  326. if (!this.editingImage || !this.newTag.trim()) return;
  327. if (!this.editingImage.tags) {
  328. this.editingImage.tags = [];
  329. }
  330. if (!this.editingImage.tags.includes(this.newTag.trim())) {
  331. this.editingImage.tags.push(this.newTag.trim());
  332. }
  333. this.newTag = '';
  334. }
  335. // 移除标签
  336. removeTag(index: number) {
  337. if (this.editingImage?.tags) {
  338. this.editingImage.tags.splice(index, 1);
  339. }
  340. }
  341. // 保存编辑
  342. saveEdit() {
  343. if (!this.editingImage) return;
  344. const index = this.referenceImages.findIndex(img => img.id === this.editingImage!.id);
  345. if (index > -1) {
  346. this.referenceImages[index] = { ...this.editingImage };
  347. this.filterByCategory(this.activeFilter);
  348. this.imagesUpdated.emit(this.referenceImages);
  349. }
  350. this.closeEditDialog();
  351. }
  352. // 删除图片
  353. deleteImage(image: ReferenceImage, event: Event) {
  354. event.stopPropagation();
  355. if (confirm(`确定要删除 "${image.name}" 吗?`)) {
  356. this.referenceImages = this.referenceImages.filter(img => img.id !== image.id);
  357. this.filterByCategory(this.activeFilter);
  358. this.imagesUpdated.emit(this.referenceImages);
  359. }
  360. }
  361. // 打开AI助手
  362. openAIAssistant(image: ReferenceImage, event: Event) {
  363. event.stopPropagation();
  364. this.aiImage = { ...image };
  365. this.showAIDialog = true;
  366. }
  367. // 关闭AI对话框
  368. closeAIDialog() {
  369. this.showAIDialog = false;
  370. this.aiImage = null;
  371. this.customQuestion = '';
  372. }
  373. // 应用AI建议的问题
  374. applyAIQuestion(question: AIQuestion) {
  375. // 这里可以实现将问题发送给客户的逻辑
  376. console.log('应用AI问题:', question);
  377. // 如果问题有关联的分类,自动添加到图片分类中
  378. if (question.category && this.aiImage) {
  379. if (!this.aiImage.categories) {
  380. this.aiImage.categories = [];
  381. }
  382. if (!this.aiImage.categories.includes(question.category)) {
  383. this.aiImage.categories.push(question.category);
  384. }
  385. }
  386. alert(`已将追问发送给客户:\n\n${question.example}`);
  387. }
  388. // 发送自定义问题
  389. sendCustomQuestion() {
  390. if (!this.customQuestion.trim()) {
  391. alert('请输入追问内容');
  392. return;
  393. }
  394. // 这里实现发送自定义问题的逻辑
  395. console.log('发送自定义问题:', this.customQuestion);
  396. alert(`已将追问发送给客户:\n\n${this.customQuestion}`);
  397. this.customQuestion = '';
  398. }
  399. // 应用AI建议并编辑
  400. applyAISuggestionsAndEdit() {
  401. if (this.aiImage) {
  402. this.closeAIDialog();
  403. this.editImage(this.aiImage, new Event('click'));
  404. }
  405. }
  406. // 预览图片
  407. previewImage(image: ReferenceImage) {
  408. this.previewImageUrl = image.url;
  409. this.showPreviewDialog = true;
  410. }
  411. // 关闭预览
  412. closePreviewDialog() {
  413. this.showPreviewDialog = false;
  414. this.previewImageUrl = '';
  415. }
  416. }